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