
Effectful Haskell: IO, Monads, Functors - brassybadger
http://slpopejoy.github.io/posts/Effectful01.html
======
nilved
Naming is one area where Haskell's academic background is more curse than
blessing. Numerous tutorials and blog posts would never have been written if
only functor was named mappable and monad was named computation builder.
They're named by analogy to category theory, but their usage in computer
science is not at all similar.

~~~
ajtulloch
The Haskell monad typeclass _is_ (modulo technical details) a category-
theoretic monad on the category `Hask`.

As Edward Kmett says in more detail in [1] - the point of calling a monad a
monad is not to confuse the reader, but to unlock 70 years of documentation on
the concept for the reader. How many programming concepts/tools/libraries do
you use that have 70 years of documentation?

Being abstract is profoundly different from being vague.

[1]: [https://yow.eventer.com/yow-2014-1222/stop-treading-water-
le...](https://yow.eventer.com/yow-2014-1222/stop-treading-water-learning-to-
learn-by-edward-kmett-1750) (around the 20 minute mark).

~~~
thinkpad20
To be fair, the great majority of that 70 years of documentation is generally
going to be completely incomprehensible to most programmers not named Edward
Kmett. And, of course, there's a lot of debate about how much practical use CT
has in day-to-day programming. Not that I don't get your point.

------
imh
Here's the next article in the series:

[http://slpopejoy.github.io/posts/Effectful02.html](http://slpopejoy.github.io/posts/Effectful02.html)

------
bshimmin
I was interested and pleased to note that the source for this post is
available in Literate Haskell:
[https://github.com/slpopejoy/tatterdemalion/blob/working/pos...](https://github.com/slpopejoy/tatterdemalion/blob/working/posts/Effectful01.lhs)

~~~
spopejoy
working link
[https://github.com/slpopejoy/tatterdemalion/blob/master/post...](https://github.com/slpopejoy/tatterdemalion/blob/master/posts/Effectful01.lhs)

------
jtaylor100
This article has successfully caused me to understand what a monad is.

------
lobster_johnson
Something I've wondered about: How does Haskell not optimize away IO ()? In a
pure, functional languages, a function that doesn't return anything can simply
be eliminated — but of course it isn't.

I've always assumed that Haskell's compiler has a built-in rules about IO
having side effects, but I never actually bothered to find out.

When I first started with Haskell, one of many small epiphanies was when I
realized that the IO monad itself doesn't actually _do_ anything. bind and
return don't do anything except wrap and unwrap values, and there's nothing
that checks if you are in some kind of "IO context" to permit things like file
I/O. There's no magical "side-effectful computation chain engine" behind the
scenes.

~~~
erydo
IO is the type, not the value. Something that takes IO and returns IO wouldn't
be optimized out for the same reason a negation wouldn't be optimized out
simply because it takes an Int and returns an Int.

~~~
lobster_johnson
I was referring to IO (), which is a type that can have only one value, () —
hence my thinking that a compiler should simply optimize the call to (), since
there is no other logical value that it can return.

~~~
groovy2shoes
The type isn't the same as its parameter. An IO () is not () just like Maybe
() isn't (), and [()] isn't ().

~~~
lobster_johnson
I'm not sure I find that explanation satisfactory. This only implies that
there is information not used by the compiler: The only conceivable value that
the type IO () can have is still the value IO (). If the compiler knew this,
then it could, and would, optimize it away. Is there a formal aspect of
parameterized types that prevents this from — formally — happening?

~~~
cousin_it
There's no such thing as "the value IO ()". The type IO () has tons of
possible values, which all have different internal representations:

1) The IO action that does nothing.

2) The IO action that prints the string "Hello World" and then returns
nothing.

3) The IO action that reads a string from the console, reverses it, prints it
back, and then returns nothing.

4) ...

It's a common misconception to think that IO X is a wrapper around X with some
"type system magic" to prevent it from being used outside IO. It's nothing
like that. For example, IO String is not a wrapper around a String, doesn't
contain any String inside, and cannot be converted to String. Meditate on the
fact that System.IO.getLine is not a function, but a value of type IO String.
Then you will understand why IO () has tons of possible values.

~~~
lobster_johnson
Thanks. I get that part. After digging a bit, it seems the implementation of
IO is in fact a bit magical [1] (and not possible to implement in pure
Haskell). GHC's IO is implemented using a GHC-internal pseudo-state monad that
uses a magical "world" value to enforce linear ordering — all of which, I'm
guessing, would prevent the compiler from accidentally optimizing away or
memoizing side-effectful functions?

Edit: I find it interesting that Haskell tutorials and books generally don't
try to peel away some of Haskell's abstractions. Many texts will tell you that
"IO a" represents an "action" and expect you to take this at face value,
intead of explaining how this is distinguishable from a mere function, and how
it's implemented internally. For example, I would say one of the big eureka
moments for any Haskell learner is to realize how lazy-evaluated graphs of
functions can turn into linearly-ordered programs thanks to the transparent
"baton passing" of monad chaining (IO in particular). But I've yet to find a
text which articulates this idea very well.

[1]
[http://stackoverflow.com/a/9244715/632555](http://stackoverflow.com/a/9244715/632555)

~~~
cousin_it
GHC's implementation of IO isn't the only one possible. For example, if
Haskell had only two available IO operations (read a character or print a
character), the following would be a workable definition of the IO type:

    
    
        data IO a = PutChar (Char, IO a)
                  | GetChar (Char -> IO a)
                  | Return a
    

Programs can work by constructing a value of type IO () and defining it as
main, just like in regular Haskell. Note that IO is no longer a wrapped impure
function, and there are no magical tokens in sight. In fact, the above
implementation doesn't even have to be opaque, it can be completely exposed to
programmers. Nevertheless, it's just as easy for the runtime to interpret in a
sequential way, and there won't be any problems with accidentally optimizing
away or memoizing anything.

------
heroku
"We can see therefore how Monad offers strictly more powerful transformations
than Functor. fmap can only transform an individual value “in place”, while
>>= can return an empty list, or a list with more elements than the original.
Functor is “structure preserving”, while Monad is “structure transforming”."

So for a list monad bind = flatMap fmap = map

is that correct?

Studying FP for quite a while first monad tutorial I come to fully understand
finally.

~~~
chriswarbo
> So for a list monad bind = flatMap fmap = map

> is that correct?

Yes, although Haskell calls "flatMap" "concatMap". Of course "return" is just
"\x -> [x]", AKA "(: [])".

Instead of bind (">>="), you can implement Monads using "join" instead. For
lists, "join" is simply "concat".

------
aikah
So is it possible to explain monads with Javascript ? or even LISP ? seems to
me monads == haskell so if one doesn't understand haskell one can't understand
monads , cause all monad tutorials are written in haskell. So I ask , what is
the point of learning what a monad is if i'm writing some Java ?

~~~
spion
Yes. Infact, a JavaScript Promise is pretty much the same as the IO monad* -
the `then` method is a lot like `bind`. Promise.resolve is a lot like `return`

* almost the same to IO, because of recursive thenable assimilation, something that was added to promises but is totally short-sighted and unnecessary; and because promises are eager which means they don't represent the action to be executed, but the value of an already executed action

~~~
aikah
A concise and good explanation thanks.

~~~
spion
Note that the word "monad" itself refers just to the "shape" of the object
(and some rules about that shape), not the functionality.

For example, an array can be a monad if we define a bind method as map +
flatten

    
    
      Array.prototype.flatMap = function(f) {
        return _.flatten(this.map(f))
      }
    

which works like so:

    
    
      allArticles = authors.flatMap(author => author.articles);
    

return would be

    
    
      Array.of = function(val) { return [val]; }
    

The shape (type) is the same:

the promise's bind (then) method takes a function that takes a value and
returns a promise, and returns another promise

the array's bind (flatMap) method takes a function that takes a value and
returns an array, and returns another array

the promise's return (Promise.resolve) method takes a value and wraps it in a
promise

the array's return (Array.of) method takes a value and wraps it in an array.

You can get the array method types (shapes) from the promise ones by replacing
promise with array (if you decide to give the methods the same name).

The implemented functionality, however, is vastly different.

