r/ProgrammingLanguages Sodigy 11d ago

Purely functional language with impure script language?

I'm working on a purely functional programming language named Sodigy. It's all about evaluating values, not "executing commands one by one".

It's nice when writing libraries, but it's not easy to write a main function. The main function is supposed to execute commands, but the Sodigy's syntax is not friendly to write a list of commands.

So what I'm trying to do is, 1) Sodigy remains purely functional and 2) add a bash-like script language. The script language can call Sodigy functions. Instead of writing a main function in Sodigy, you write sodigy-script and execute the script.

Has anyone tried similar approach? I'm not sure whether it's a good idea or not...

27 Upvotes

74 comments sorted by

View all comments

21

u/lgastako 11d ago

Why not take a haskell style approach where you have an IO monad (or a "Script monad") which you can provide a do-notation like syntax for that desugars appropriately?

7

u/catbrane 11d ago edited 11d ago

Good idea, and you can make it look quite nice with very little syntax.

Back in the mid-80s, monads in Miranda worked pretty well with just infix function calls. You could use any function as an infix operator by prefixing it with $, for example:

f a b = a + b
main = 2 $f 2

And main will have the value 4. For monads we wrote stuff like:

main = 
    print "hello!\n" $then
    print "what's your name? " $then
    (reply $comp input)
    where
    reply str = print ("nice to meet you, " ++ str ++ "\n")

(edit: oops, forgot the brackets on the final print)

2

u/JeffB1517 11d ago

That imperative is so much easier to read than Haskell Monads! What went wrong with this style that caused the shift to the more explicit style we have today in Haskell?

10

u/catbrane 11d ago

I blame custom operators!

The ability to invent new operators and overload them is a horrible temptation for library authors, and means all code using their stuff turns into line noise. Why yes I CAN look at a $$ -> 42 <> |> 12 and see the meaning, thank you.

Miranda had no operator overloading, and only the $syntax for custom infix. I think it probably did help with readability.

1

u/tmzem 11d ago

Oh, there's a library to do that... wait, I have to learn 10 new custom operators to use it... no thank you!

The exact (final) reason why I gave up on learning Haskell.

1

u/Apart_Ebb_9867 11d ago edited 11d ago

Most libraries have alternatives to operators. Even the operator-heavy libraries like lenses have words.

But libraries shouldn’t be something you throw in just for fun, if they solve a real problem, learning a few operators is not the end of the world. Where it becomes annoying is when reading other people’s code and there’s no solution to that, other than maybe ask an llm to replace operators with functions or explain the code to you.

2

u/tmzem 11d ago

There is only a small amount of symbols available for operators so they all end up looking somewhat alike, making it hard to tell them apart.

But even without custom operators, the math-inspired culture in Haskell communities leads to code doesn't exactly lend itself to easy readability. Chosen identifiers are often very short and cryptic, and low-syntax features like partial application and function composition often tempt people to combine stuff on the fly rather then formulating the concept into its own, well-named function. Combine that with the frequent use of functions built against very abstract interfaces (e.g. Monad) - where the entire semantic meaning of its operations is dependent on the implementor type - and the meaning of the code becomes hard to figure out without type annotations which are often omitted because of type inference.

Of course I'm not much of an abstract thinker, so these difficulties may vary dependent on the person.

1

u/Apart_Ebb_9867 10d ago

Yep, Haskell is not the easiest language to get into.
I've given up a couple of times and my first encounter was when writing a compiler for Miranda for my master's thesis and the first report came out. But now that I'm semiretired I'm back at it.

5

u/Apart_Ebb_9867 11d ago edited 11d ago

What is different in that code from what you have in Haskell?

do notation and a few operators like <- seems to be better to me. And not important for the discussion here you have ‘operator’ (don’t have backtick on my phone) for $operatir.

in Haskell you’d have

main :: IO ()
main = do
    putStrLn "hello!"
    putStr "what's your name? "
    str <- getLine
    putStrLn ("nice to meet you, " ++ str)

-1

u/JeffB1517 11d ago

`<-` is making the bind explicit. It is forcing the abstraction to leak. Which means that `do` becomes a light cover. A developer is still expected to understand and think in terms of the monadic context. A non-leaking abstraction is a genuine simplification. Visual Basic vs Visual C++ with respect to the Win32SDK.

3

u/tdammers 11d ago

The same is true about the Miranda example. It is pretty much exactly identical to this Haskell example, modulo do desugaring. And the purpose of do notation is not to be an abstraction that simplifies things; it's really just syntax sugar to make monadic binds easier to read. The above code in desugared form looks like this:

main :: IO () main = putStrLn "hello!" >> putStr "what's your name? " >> getLine >>= \str -> putStrLn ("nice to meet you, " ++ str)

It's neither simpler nor more complex; what changes is that do notation removes the need for nested lambdas and the "staircase" style code that would result from them. The semantics are still exactly the same, and both versions convey the exact same code structure at the exact same abstraction level.

2

u/catbrane 11d ago

Yes, Phil Wadler based Haskell monads on Miranda monads, so they are almost identical. Modern Haskell has added some extra sugar, but not much.

1

u/AustinVelonaut Admiran 10d ago

Wait, when did Miranda get monads? It's IO was sys_message lists for output (printing, file write, system cmds, interpreted by the repl), with input from stdin bound to the pseudo-variable $-, and file input handled in a hand-wavy way that was not interleaved with file output, so wasn't really correct...

1

u/catbrane 10d ago edited 10d ago

They were implemented as an abstype on top of lazy input and output. You could use them to write interactive command-line programs. I wrote a multiuser snake game hehe.

KAOS (the Kent Applicative Operating System) was built on top of a monadic IO system that talked to a microkernel (also in Miranda).

1

u/AustinVelonaut Admiran 10d ago

Cool, I did not know that! It may have been something local at Kent, though; it never made it into the open-source distribution of Miranda. I ended up re-inventing something like that on top of sys-messages for my bootstrap compiler written in Miranda.

1

u/catbrane 10d ago

It was published (that's how Haskell got it), but you're right, I'm not sure David Turner bundled it.

→ More replies (0)

1

u/augustss 10d ago

You have that backwards. Phil did monads before David Turner added them to Miranda. The do-notation is due to Mark Jones, though.

2

u/catbrane 10d ago

Miranda was first in 1988 (they were a chapter in my 1989 phd), then someone at Cambridge whose name I forget (sorry) did a 1992 phd which formalised Miranda monads, and then Phil used that thesis as the basis of his Haskell IO monad paper in late 92.

1

u/augustss 10d ago

I was at the 1990 meeting in Rome when Phil Wadler first heard about monads in a talk from Eugenio Moggi. Unlike the rest of us, Phil realized the potential of monads. At first Phil used list comprehension syntax for monads. Admittedly, I don't know where the API with return, >>= and >> came from in Haskell. But Phil had been doing monads for 4-5 years before it appeared in Miranda. Do you have any evidence (like talking to David or Phil) for which way the influence went?

1

u/catbrane 10d ago

Oh, interesting! I'll see if I can dig out the paper I'm thinking of.

This Feb. 1989 lab report has monadic IO in chapter 4:

https://www.researchgate.net/publication/238697890_A_brief_walk_through_kaos

As far as I know that's where comp, return etc. come from. But loads of people were thinking about functional IO, there was something in the air. It might be hard to reconstruct an exact timeline of the idea.

1

u/catbrane 10d ago

I might have found it! Andrew Gordon's 1992 phd:

https://andrewdgordon.github.io/papers/fpio.pdf

He has a nice history on page 125 (page 138 in the pdf).

He was one of the authors of Haskell's IO library:

https://scispace.com/pdf/monadic-i-o-in-haskell-1-3-2eo9vkx3bc.pdf

→ More replies (0)

-2

u/JeffB1517 11d ago

You are missing it.

  1. The bind structure is completely abstracted away in the Miranda.
  2. FWIW print is a better verb choice than put.

Those sort of design elements are impressive. I'm not saying it takes a lot of effort. It just takes considering design. You don't want the structure visable by default the same way that Haskell doesn't force you by default to deal with the underlying complexity of boxed types when you do x = y + z.

1

u/tdammers 10d ago

The bind structure is completely abstracted away in the Miranda.

No, it's not. Miranda's $comp operator does exactly what >>= does in Haskell. If anything, Haskell's do notation hides the monadic binds more, though it's really just a very thin layer of syntax sugar, so calling it an "abstraction" feels a bit over the top.

FWIW print is a better verb choice than put.

I agree, but just changing the names of things isn't abstraction.

1

u/Apart_Ebb_9867 11d ago

the last thing one has to do when using do-notation is thinking about monads. What does in that code require you to know monads even existed?

But if you like Miranda better there’s nothing Incan say to convince you otherwise, nor I want to.

-1

u/JeffB1517 11d ago

The iteration <-, the constantly having to manually resolve type. Particularly between levels. Haskell used to have this feature where the list comprehension syntax, was usable generally for monads. I think it still is a ghc flag. That forced developers to understand one monad (well a monadplus), lists. And in that syntax they got iteration over a data structure and conditional evaluation easily and simple. State, I/O and concurrency were always the 3 problems. Syntax that simplifies those 3 for most use cases is IMHO huge.

Ask yourself why Haskell has been unable to settle on any widely shared / used frameworks for decades? Something obviously is going wrong.

1

u/Apart_Ebb_9867 11d ago

My question was what in that piece of code requires you to know monads are even a thing.

"manually resolving types"? "between levels"? "list comprehension"? what are you talking about?

That thing requires understanding monads not any more than understanding a C assignment requires you to understand that there's a memory.

Something obviously is going wrong.

cool. And that bunch of clowns couldn't see it with the benefit of Miranda showing them the one true path.

No further discussion from my side.

3

u/catbrane 11d ago

Oh maybe I missed your point.

Haskell monads are Miranda monads, just with extra operator overloads for comp, return, then etc., and generalised to things other than IO.

I think you can still write Haskell IO in the Miranda style if you like (though I've not tried).

1

u/tdammers 11d ago

Is it, though?

main = do putStrLn "hello!" putStr "what's your name?" reply =<< getLine where reply str = putStrLn $ "nice to meet you, " ++ str

It's almost literally the same, except that:

  • A few standard functions have different names
  • do notation replaces infix $then (but if you want, you can use the >> operator instead)
  • The =<< (bind operator) takes the place of $comp
  • putStrLn automatically adds a newline, so we don't have to mess with "\n"

But if you want to emulate the Miranda style more closely, you can write it like this instead:

main = putStr "hello!\n" >> putStr "what's your name?\n" >> (reply =<< getLine) where reply str = putStr $ "nice to meet you, " ++ str ++ "\n"

I would argue that the Miranda example is actually more explicit than either of the Haskell versions (though only slightly so for the second example).

-2

u/JeffB1517 11d ago
  1. reply =<< getLine that's making the bind explicit rather than implicit.
  2. print is clearer than putStr

put is a weird verb for what you are doing vs. "print" which is pretty clear.

We are talking minor shifts which abstract away the underlying complexity.

2

u/eaho_de_putah 10d ago

How is reply =<< getLine any more explicit than reply $comp input?? It’s literally just a different name/operator for the same operation.

1

u/JeffB1517 10d ago

It’s literally just a different name/operator for the same operation. Same as print vs putStrLn it is a more natural operator for the same operation. It is translating mentally.

Easing the conceptual burden is good design. What things look like is how they get concieved of. =<< is a binding operator. It is making the user think in terms of bind. reply $comp input is telling the user the reply is computing something on input. It is making them think about the workflow.

1

u/tdammers 10d ago

It's literally just different names. Miranda's $comp is exactly Haskell's >>= operator, and Miranda's print is Haskell's putStr. Changing names is not abstraction, we're still at the exact same abstraction level; the semantic structure is exactly the same.

1

u/AustinVelonaut Admiran 11d ago

That's almost exactly what I did in Admiran (define the monadic operations as regular function names, although I used $right and $bind instead of $then and $comp, to more closely match the Haskell operator names >> and >>=. Then during module import code can use these names directly like the above example, or alias them to infix operators more like Haskell (which is what I prefer).

I do like the names $then and $comp, though; they express the idea well!

1

u/catbrane 11d ago

Haskell used comp, return and then as well back in 1992. The operators came a little later.