This is one of the funniest thing’s I’ve heard in a while! So true!
This also explains why each individual person think’s their own explanation of Monads is the best one and that everyone else’s explanation is confusing.
In all seriousness, my Haskell buddies do tend to agree that (at least when learning Haskell) most people confuse the Monad type class with specific instances of the class. The IO type and the Maybe type are both function types, and both are members of the Monad type class. People may end up trying to make a function of type “Monad,” which would result in a compile-time error, because Monad isn’t a type in Haskell, it’s a type class.
My favorite explanation for people coming from an imperative background, especially from C, C++, Java, or JavaScript, is that “Monads are a way to overload the semicolon operator.” As in, you define what a procedure does between each step of the procedure.
Ok still scratching my head over that one. I think the issue had to do with the basic constructs of the language. In JavaScript we don’t have any high order typing constructs so we invent them by wrapping data inside objects which have an API or interface.
Haskell has “do” notation for writing procedural code, but “do” is merely syntactic sugar for monadic binding. For example
main = do {
file <- readFile path ;
buf <- newListArray file ;
mapArray buf (pure code) ;
}
can also be written as (and is desugared to)
main = readFile path >>= ( file ->
newListArray file >>= ( buf ->
mapArray buf (pure code) ) )
The >>= operator is monadic bind, and this particular monad happens to be of type IO.
But if we wanted to, we could define what you could call a “super-type” of IO, one that might contain stateful references to file handles and also might contain exception handling code, or parallel computation code.
Then we instantiate our “super-type” into the Monad class so we can write functions of our type using “do” notation. So when we write our “do” procedure, we can take advantage of these other features incorporated into our custom monad.
Ok I’m getting the semicolon thing. A more familiar paradigm for this kind of thing is a chain of methods (jquery, low dash) or a Unix shell pipeline.
Monadic JavaScript tends to be arranged as chains. There’s an emerging spec for the JavaScript API called fantasyland. github.com – fantasyland/fantasy-land
You could think of monads as being similar to shell piping. In this analogy, “binding” the function output to a variable would be like dumping command output to a file in the current directory so that the contents of the file can be used as parameters to function calls further down the procedure. Once a variable is bound, it remains in scope to all further functions in the procedure. For example:
a <- getLine
putStrLn (a ++ a)
is similar to:
cat >a.txt
echo $(cat a.txt)$(cat a.txt)
Except in Haskell, the value stored in “a” (the “a.txt” file) would disappear as soon as it goes out of scope.
In JavaScript, the variable scoping syntax for lambdas would you require you to write another level of nested parentheses for each bind, or else your variables will go out of scope and you won’t be able to use them at later stages in the procedure or pipeline. This is one reason why Haskell’s syntax, especially do notation, makes using monads so effortless compared to languages which were not designed to be functional.
However, there is no free lunch. It is very important to know the semantics of the monad you are using. Not all monads behave like shell pipes.
In Haskell, using the IO monad as you would shell pipes is not always a good idea. In spite of laziness, many IO type functions are required to buffer all input before proceeding to the next step. GHC is smart enough to do the right thing most of the time when working with POSIX file stream handles, but as the user of the monad, it is your responsibility to read the docs and make sure you understand what the “semicolons” are really doing behind the scenes.
The “conduit” library provides a set of monadic functions carefully crafted to behave more like shell pipes by providing a “yeild” and “await” functions, and the “.|” pipe operator. Conduit gives you the option of extending other monadic types (like the IO function type, or your own custom monad types) with shell piping capabilites, or you can define pure functions using shell-pipe semantics while still using “do” notation. It also uses static typing to ensure the protocol of the elements being output by a producer is compatible with the protocol of elements that can be accepted by the consumer.
This article makes a pretty good effort medium.com – JavaScript Monads Made Simple – JavaScript Scene – Medium
LikeLike
This is one of the funniest thing’s I’ve heard in a while! So true!
This also explains why each individual person think’s their own explanation of Monads is the best one and that everyone else’s explanation is confusing.
In all seriousness, my Haskell buddies do tend to agree that (at least when learning Haskell) most people confuse the Monad type class with specific instances of the class. The IO type and the Maybe type are both function types, and both are members of the Monad type class. People may end up trying to make a function of type “Monad,” which would result in a compile-time error, because Monad isn’t a type in Haskell, it’s a type class.
My favorite explanation for people coming from an imperative background, especially from C, C++, Java, or JavaScript, is that “Monads are a way to overload the semicolon operator.” As in, you define what a procedure does between each step of the procedure.
LikeLike
Ok still scratching my head over that one. I think the issue had to do with the basic constructs of the language. In JavaScript we don’t have any high order typing constructs so we invent them by wrapping data inside objects which have an API or interface.
LikeLike
John Hardy Turnbull delenda est
Haskell has “do” notation for writing procedural code, but “do” is merely syntactic sugar for monadic binding. For example
main = do {
file <- readFile path ;
buf <- newListArray file ;
mapArray buf (pure code) ;
}
can also be written as (and is desugared to)
main = readFile path >>= ( file ->
newListArray file >>= ( buf ->
mapArray buf (pure code) ) )
The >>= operator is monadic bind, and this particular monad happens to be of type IO.
But if we wanted to, we could define what you could call a “super-type” of IO, one that might contain stateful references to file handles and also might contain exception handling code, or parallel computation code.
Then we instantiate our “super-type” into the Monad class so we can write functions of our type using “do” notation. So when we write our “do” procedure, we can take advantage of these other features incorporated into our custom monad.
LikeLike
Ok I’m getting the semicolon thing. A more familiar paradigm for this kind of thing is a chain of methods (jquery, low dash) or a Unix shell pipeline.
Monadic JavaScript tends to be arranged as chains. There’s an emerging spec for the JavaScript API called fantasyland. github.com – fantasyland/fantasy-land
LikeLike
You could think of monads as being similar to shell piping. In this analogy, “binding” the function output to a variable would be like dumping command output to a file in the current directory so that the contents of the file can be used as parameters to function calls further down the procedure. Once a variable is bound, it remains in scope to all further functions in the procedure. For example:
a <- getLine
putStrLn (a ++ a)
is similar to:
cat >a.txt
echo $(cat a.txt)$(cat a.txt)
Except in Haskell, the value stored in “a” (the “a.txt” file) would disappear as soon as it goes out of scope.
In JavaScript, the variable scoping syntax for lambdas would you require you to write another level of nested parentheses for each bind, or else your variables will go out of scope and you won’t be able to use them at later stages in the procedure or pipeline. This is one reason why Haskell’s syntax, especially do notation, makes using monads so effortless compared to languages which were not designed to be functional.
However, there is no free lunch. It is very important to know the semantics of the monad you are using. Not all monads behave like shell pipes.
In Haskell, using the IO monad as you would shell pipes is not always a good idea. In spite of laziness, many IO type functions are required to buffer all input before proceeding to the next step. GHC is smart enough to do the right thing most of the time when working with POSIX file stream handles, but as the user of the monad, it is your responsibility to read the docs and make sure you understand what the “semicolons” are really doing behind the scenes.
The “conduit” library provides a set of monadic functions carefully crafted to behave more like shell pipes by providing a “yeild” and “await” functions, and the “.|” pipe operator. Conduit gives you the option of extending other monadic types (like the IO function type, or your own custom monad types) with shell piping capabilites, or you can define pure functions using shell-pipe semantics while still using “do” notation. It also uses static typing to ensure the protocol of the elements being output by a producer is compatible with the protocol of elements that can be accepted by the consumer.
hackage.haskell.org – Data.Conduit
LikeLike