slack1
2017-12-29 22:51:51

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


slack1
2017-12-29 22:52:27

Ah god the burrito


slack1
2017-12-29 22:52:30

I understand now


brendan
2017-12-29 22:54:18

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


brendan
2017-12-29 22:54:26

This explanation is probably wrong


slack1
2017-12-29 22:54:40

Oh I see, so it’s for concurrency control


slack1
2017-12-29 22:54:54

it’s for serial computations


brendan
2017-12-29 22:55:14

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


brendan
2017-12-29 22:55:51

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


brendan
2017-12-29 22:56:32

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


brendan
2017-12-29 22:57:00

(in a strict language it will print Hello World)


slack1
2017-12-29 22:59:00

So a monad is good at evaluating conditionals for alternative computations


brendan
2017-12-29 22:59:15

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


brendan
2017-12-29 22:59:30

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


slack1
2017-12-29 23:00:03

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


brendan
2017-12-29 23:01:25

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


brendan
2017-12-29 23:01:36

And then to evaluate that in your main function


brendan
2017-12-29 23:03:43

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


slack1
2017-12-29 23:04:39

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


brendan
2017-12-29 23:05:13

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


slack1
2017-12-29 23:05:25

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


brendan
2017-12-29 23:05:27

The opposite. It allows you to enforce strictness


brendan
2017-12-29 23:05:41

What do you mean by global concept?


slack1
2017-12-29 23:05:54

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


notjack
2017-12-29 23:06:17

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


brendan
2017-12-29 23:06:23

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


brendan
2017-12-29 23:06:43

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


slack1
2017-12-29 23:07:54

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


brendan
2017-12-29 23:08:25

Not afaik


brendan
2017-12-29 23:08:57

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


notjack
2017-12-29 23:09:22

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


brendan
2017-12-29 23:09:57

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


slack1
2017-12-29 23:10:08

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


slack1
2017-12-29 23:10:58

in terms of programming motivation to reach for a tool


notjack
2017-12-29 23:10:59

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


notjack
2017-12-29 23:11:17

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


slack1
2017-12-29 23:11:40

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


slack1
2017-12-29 23:12:02

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


slack1
2017-12-29 23:12:08

So why isn’t it relevant to Racket?


notjack
2017-12-29 23:12:42

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


notjack
2017-12-29 23:12:56

which is not unreasonable


slack1
2017-12-29 23:13:12

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


slack1
2017-12-29 23:14:26

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


lexi.lambda
2017-12-29 23:14:32

notjack
2017-12-29 23:14:43

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 !


slack1
2017-12-29 23:35:05

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


lexi.lambda
2017-12-29 23:46:42

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.


lexi.lambda
2017-12-29 23:47:54

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.


lexi.lambda
2017-12-29 23:48:39

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.


notjack
2017-12-29 23:49:05

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


notjack
2017-12-29 23:49:27

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


notjack
2017-12-29 23:49:34

(or at least can be)


lexi.lambda
2017-12-29 23:49:41

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.


slack1
2017-12-29 23:52:57

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


lexi.lambda
2017-12-29 23:53:19

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.


lexi.lambda
2017-12-29 23:54:20

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).


notjack
2017-12-29 23:54:22

like a thunk


lexi.lambda
2017-12-29 23:58:27

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)))]))


slack1
2017-12-29 23:59:12

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


notjack
2017-12-29 23:59:17

basically yeah


lexi.lambda
2017-12-29 23:59:29

Right… async/await forms a monad. :)


lexi.lambda
2017-12-29 23:59:51

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.


lexi.lambda
2017-12-30 00:00:35

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.


lexi.lambda
2017-12-30 00:01:14

(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.)


notjack
2017-12-30 00:02:19

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


lexi.lambda
2017-12-30 00:03:10

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.


lexi.lambda
2017-12-30 00:04:21

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.


slack1
2017-12-30 00:29:21

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


lexi.lambda
2017-12-30 00:30:30

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


slack1
2017-12-30 00:30:52

I see


slack1
2017-12-30 00:31:36

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