4 ms·
The IO monad is just an action. Its a cons pair with a left and a right half. In an imperative language you could model the left half with a closure that takes
by spion 12y ago
The IO monad is just an action. Its a cons pair with a left and a right half. In an imperative language you could model the left half with a closure that takes no arguments, does I/O and produces a value.
The right half is usually empty, except when you use the `>>=` operator (or flatMap) on an existing action, e.g.
let c = a >>= f
That operator chains the function `f` can take the value produced by the first IO action and produce another IO action. After we apply that operator, the result `c` is a cons cell where the left part is the original action `a` and the right part is the function f which takes the value produced by a and produces the next action. Its sort of like a cons cell in regular lists, except the next value is provided by a function. A lazy cons cell, perhaps :)
The final result is a lazy chain of I/O cons cells (called "main", of course :P). Its passed to the Haskell runtime, which executes that chain as a recipe, alternating between doing I/O actions and evaluating the function to decide what to do next.
So what does this buy us? Mostly just referential transparency. What does referential transparency buy us? Easy refactoring. We can replace any expression with its value, even stuff like `putStrLn "test"`. We can say `let writeTest = putStrLn "test"` at the top of the file then write `do writeTest; writeTest` in main.
Another neat thing is that do syntax isn't limited to just IO, but works with anything that implements `flatMap` (and `unit`, which I forgot to mention). That means we can build our own imperative DSLs that produce IO-like monads which are then interpreted by our own interpreter, and the users of those DSLs can use the same do syntax. Which is pretty awesome. Here is a simple example: https://gist.github.com/tonymorris/b5dba9d7d877051d0164 https://gist.github.com/tonymorris/b5dba9d7d877051d0164 and a much more complex one http://augustss.blogspot.com/2009/02/more-basic-not-that-anybody-should-care.html http://augustss.blogspot.com/2009/02/more-basic-not-that-any... :)
- spion 12y agoor these PureScript examples https://gist.github.com/spion/982350f4b3d3464b1870 https://gist.github.com/spion/982350f4b3d3464b1870 using the Canvas monad to pre-create a scene and then render it; the DOM monad to construct DOM elements.
- jbooth 12y agoSo how are errors handled?
- spion 12y agoYou have the option of two monads, Maybe and Either. Usually errors are handled with the Either monad. An operation may either return a result or an error. Together with do syntax the Either monad gives you multiple choices in handling errors. You can handle every single error explicitly (as in Go) using pattern matching eitherResultOrError = operation1 arg case eitherResultOrError of Left error -> handle error Right result -> handle' result Its also possible to chain multiple operations then check the error later. If an error occurs, the next operations in the chain will not execute. let eitherResultOrError = do x <- operation1 arg y <- operation2 x + 1 z <- operation3 x y case eitherResultOrError of Left error -> handle error Right result -> handle' result Or simply use `orElse` to return a default value in case of errors. For IO operations and other monadic actions, its best to use EitherT, ErrorT or MaybeT, which are monads that can add error handling to any other monad. To understand how these work, its probably best to implement MaybeT. Basically, they add another wrapper to other monads to redefine what the bind operator (`>>=`) does