
Is there a canonical article to read on Monads and why programmers are interested with respect to IO?

Ah god the burrito

I understand now

Programmers are interested in using Monads for IO in lazy languages because certain types of monads (state monads) allow you to enforce an evaluation order

This explanation is probably wrong

Oh I see, so it’s for concurrency control

it’s for serial computations

No to the first, yes to the second. This is only when talking about the IO monad though! Not in general!

Consider this expression let _ = print "Hello " in print "World"

In a lazy language, this will print World
when evaluated. The print "Hello"
expression isn’t used, so it isn’t evaluated

(in a strict language it will print Hello World
)

So a monad is good at evaluating conditionals for alternative computations

The IO monad allows us to enforce a dependency between things which otherwise wouldn’t have one. So we can do print "Hello" >> print "World"
and it’ll do what we want

No, I haven’t said anything about monads in general, just the IO monad

ah I see, I was confused, I somehow thought the IO monad was an end-all-be-all concept for effects

That perception might be because the only way to cause an effect is to create a value of type IO anything
.

And then to evaluate that in your main function

One note though is that if you have main = let x = print 1 in print 2
, you’ll only print 2. You need to sequence them with a monad operation to get the evaluation order as you expect, like print 1 >> print 2
or do print 1
print 2

I see, so it also allows you express laziness more easily

Nb: I’m using Haskell syntax because you asked about monads and IO

I thought that IO Monad is a global concept, is it not?

The opposite. It allows you to enforce strictness

What do you mean by global concept?

Like it’s a concept general to most or all FP languages?

monads do a lot of things, many of which are much more general than controlling evaluation order

Not really imo. IO is only useful in a lazy language

Yeah to reiterate I’m only talking about IO, not general monads

Ah I see, so Racket doesn’t make use of the concept

Not afaik

Hackett does, but I don’t know if it would if it were strict

it’s pure though, which seems like the bigger reason to use monadic IO

Oh yeah that’s also an important thing I left out

So in conclusion, IO Monad is most desired by programmers working in lazy languages to express strictness when needed

in terms of programming motivation to reach for a tool

I kinda wish the monad that pure FP langs used to describe “stuff that could do anything at all” was named Unsafe
instead of IO

@slack1 it’s really much more about the purity than the strictness/laziness IMO

I’m actually curious about what a pure system can look like

if it’s about purity, isn’t Monad a much more general concept?

So why isn’t it relevant to Racket?

racket’s language and core library design does not emphasize purity, only immutability-by-default

which is not unreasonable

But it seems like Racket is certainly allowing you to explore the pure realm, since most things are pure and they warn you with ! when it’s not

But I guess if IO Monad isn’t used very generally, then it’s not broad enough to be considered an architectural pattern


throwing contract exceptions is impure, so essentially if racket wanted to use ! to mean “impure” then nearly every single function would have to be named with !

Monad IO still seems like quite a general concept from that SO article

Finally able to read the previous discussion here… I think some of this is a little misguided. IO, as it exists in Haskell, is merely a mechanism to encode programs that perform effects in a referentially transparent way, which is important in a pure, lazy language.

IO happens to be a monad, but nobody would care much about that if there were not many, many other monads that people find useful. There are, so Haskell includes a more general concept of a monad, but I don’t think it is especially relevant to understanding IO.

IO, as it exists in Haskell, is also not the only way to encode I/O in a pure, lazy language, and there are many ways that are not monadic.

CML events (aka Racket events) with sync
acting as unsafePerformIO
can be seen as very similar to the haskell IO type

in that the construction of an event without syncing on it is often pure

(or at least can be)

Some of the concepts behind IO are potentially useful in a strict, impure language, but probably not for the same reasons they are in Haskell.

I see, I am interested in building a mostly pure app in a strict functional language, and getting intuition over that

If you don’t care about monads, but you do care about Haskell’s IO, just think of IO as a mechanism of encoding effects functionally by creating a data structure to be interpreted later. Instead of writing a file, construct some data in a particular shape, like (list 'write-file "foo.txt")
, then write a little language interpreter that runs on the outside of your program that translates the datastructures into actual effects. This way, your whole program is just building up a recipe to perform effects, never actually doing effects, so it’s pure.

You just need one of the primitives of your mini-language to be (then x f)
, which is interpreted by interpreting the expression x
, then passing its result to f
and interpreting the result of (f x)
.

like a thunk

Right. A program that reverses a file’s contents would be represented in the mini-language with something like this: (define (reverse-file f)
`(then (read-file ,f) ,(lambda (str) `(write-file ,f ,(reverse str)))))
…and the interpreter would be something like this: (define (interpret e)
(match e
[`(then ,e ,f) (interpret (f (interpret e)))]
[`(read-file ,path) (file->string path)]
[`(write-file ,path ,str) (with-output-to-file (lambda () (displayln str)))]))

Ah.. so it’s like a DSL for living inside a chain of async await functions

basically yeah

Right… async/await forms a monad. :)

But is this a good way to structure programs? I don’t really think so. The reason it’s useful in Haskell is that the type system enforces it, but you can’t really enforce it in a dynamically typed language, so the encoding doesn’t really get you anything.

If you’re just mapping your I/O operations directly to primitives in your I/O language, then you have not really made your program any “more functional” than it was before, imo.

(Even if it is not technically doing I/O, the above implementation of read-file
is no less imperative than it would be if it just did the side effects directly.)

contracts for purity would be nice, as a way to get gradual purity

My point is that, even though Haskell really is totally pure and referentially transparent (unsafePerformIO
notwithstanding), programs that live entirely in IO
are still quite imperative. The real thing to focus on is making the core of your program functionally designed, not functional on a technicality, and pushing the logic of how your program interacts with the outside world into a small set of operations on the boundary.

In some programs, like a data processing pipeline, this is easy. Your logic is just a bunch of pure transformations, and your I/O is just reading and writing data at the beginning and end of the pipeline. In a CRUD web app? It’s a lot harder, since every request is probably doing lots of I/O with the database or other HTTP services. But you can still get some benefits of functional programming… make the code that renders your HTML pure, for example.

By boundary I presume you mean a port for delivering and receiving effects

No, it doesn’t need to be such a rigid boundary. I just mean the code closer to the entrypoint of your program.

I see

well I think I’m getting an increasingly solid intuition on how I want to organize my toy functional app with effects