What is a monad?
Having briefly looked at Haskell recently, what would be a brief, succinct, practical explanation as to what a monad essentially is?
I have found most explanations I've come across to be fairly inaccessible and lacking in practical detail.
First: The term monad is a bit vacuous if you are not a mathematician. An alternative term is computation builder which is a bit more descriptive of what they are actually useful for.
You ask for practical examples:
Example 1: List comprehension :
[x*2 | x<-[1..10], odd x]
This expression returns the doubles of all odd numbers in the range from 1 to 10. Very useful!
It turns out this is really just syntactic sugar for some operations within the List monad. The same list comprehension can be written as:
do
x <- [1..10]
if odd x
then [x * 2]
else []
Or even:
[1..10] >>= (x -> if odd x then [x*2] else [])
Example 2: Input/Output :
do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Welcome, " ++ name ++ "!")
Both examples use monads, AKA computation builders. The common theme is that the monad chains operations in some specific, useful way. In the list comprehension, the operations are chained such that if an operation returns a list, then the following operations are performed on every item in the list. The IO monad on the other hand performs the operations sequentially, but passes a "hidden variable" along, which represents "the state of the world", which allows us to write I/O code in a pure functional manner.
It turns out the pattern of chaining operations is quite useful and is used for lots of different things in Haskell.
Another example is exceptions: Using the Error
monad, operations are chained such that they are performed sequentially, except if an error is thrown, in which case the rest of the chain is abandoned.
Both the list-comprehension syntax and the do-notation are syntactic sugar for chaining operations using the >>=
operator. A monad is basically just a type that supports the >>=
operator.
Example 3: A parser
This is a very simple parser which parses either a quoted string or a number:
parseExpr = parseString <|> parseNumber
parseString = do
char '"'
x <- many (noneOf """)
char '"'
return (StringValue x)
parseNumber = do
num <- many1 digit
return (NumberValue (read num))
The operations char
, digit
, etc. are pretty simple. They either match or don't match. The magic is the monad which manages the control flow: The operations are performed sequentially until a match fails, in which case the monad backtracks to the latest <|>
and tries the next option. Again, a way of chaining operations with some additional, useful semantics.
Example 4: Asynchronous programming
The above examples are in Haskell, but it turns out F# also supports monads. This example is stolen from Don Syme:
let AsyncHttp(url:string) =
async { let req = WebRequest.Create(url)
let! rsp = req.GetResponseAsync()
use stream = rsp.GetResponseStream()
use reader = new System.IO.StreamReader(stream)
return reader.ReadToEnd() }
This method fetches a web page. The punch line is the use of GetResponseAsync
- it actually waits for the response on a separate thread, while the main thread returns from the function. The last three lines are executed on the spawned thread when the response have been received.
In most other languages you would have to explicitly create a separate function for the lines that handle the response. The async
monad is able to "split" the block on its own and postpone the execution of the latter half. (The async {}
syntax indicates that the control flow in the block is defined by the async
monad.)
How they work
So how can a monad do all these fancy control-flow thing? What actually happens in a do-block (or a computation expression as they are called in F#), is that every operation (basically every line) is wrapped in a separate anonymous function. These functions are then combined using the bind
operator (spelled >>=
in Haskell). Since the bind
operation combines functions, it can execute them as it sees fit: sequentially, multiple times, in reverse, discard some, execute some on a separate thread when it feels like it and so on.
As an example, this is the expanded version of the IO-code from example 2:
putStrLn "What is your name?"
>>= (_ -> getLine)
>>= (name -> putStrLn ("Welcome, " ++ name ++ "!"))
This is uglier, but it's also more obvious what is actually going on. The >>=
operator is the magic ingredient: It takes a value (on the left side) and combines it with a function (on the right side), to produce a new value. This new value is then taken by the next >>=
operator and again combined with a function to produce a new value. >>=
can be viewed as a mini-evaluator.
Note that >>=
is overloaded for different types, so every monad has its own implementation of >>=
. (All the operations in the chain have to be of the type of the same monad though, otherwise the >>=
operator won't work.)
The simplest possible implementation of >>=
just takes the value on the left and applies it to the function on the right and returns the result, but as said before, what makes the whole pattern useful is when there is something extra going on in the monad's implementation of >>=
.
There is some additional cleverness in how the values are passed from one operation to the next, but this requires a deeper explanation of the Haskell type system.
Summing up
In Haskell-terms a monad is a parameterized type which is an instance of the Monad type class, which defines >>=
along with a few other operators. In layman's terms, a monad is just a type for which the >>=
operation is defined.
In itself >>=
is just a cumbersome way of chaining functions, but with the presence of the do-notation which hides the "plumbing", the monadic operations turns out to be a very nice and useful abstraction, useful many places in the language, and useful for creating your own mini-languages in the language.
Why are monads hard?
For many Haskell-learners, monads are an obstacle they hit like a brick wall. It's not that monads themselves are complex, but that the implementation relies on many other advanced Haskell features like parameterized types, type classes, and so on. The problem is that Haskell I/O is based on monads, and I/O is probably one of the first things you want to understand when learning a new language - after all, it's not much fun to create programs which don't produce any output. I have no immediate solution for this chicken-and-egg problem, except treating I/O like "magic happens here" until you have enough experience with other parts of language. Sorry.
Excellent blog on monads: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
Explaining "what is a monad" is a bit like saying "what is a number?" We use numbers all the time. But imagine you met someone who didn't know anything about numbers. How the heck would you explain what numbers are? And how would you even begin to describe why that might be useful?
What is a monad? The short answer: It's a specific way of chaining operations together.
In essence, you're writing execution steps and linking them together with the "bind function". (In Haskell, it's named >>=
.) You can write the calls to the bind operator yourself, or you can use syntax sugar which makes the compiler insert those function calls for you. But either way, each step is separated by a call to this bind function.
So the bind function is like a semicolon; it separates the steps in a process. The bind function's job is to take the output from the previous step, and feed it into the next step.
That doesn't sound too hard, right? But there is more than one kind of monad. Why? How?
Well, the bind function can just take the result from one step, and feed it to the next step. But if that's "all" the monad does... that actually isn't very useful. And that's important to understand: Every useful monad does something else in addition to just being a monad. Every useful monad has a "special power", which makes it unique.
(A monad that does nothing special is called the "identity monad". Rather like the identity function, this sounds like an utterly pointless thing, yet turns out not to be... But that's another story™.)
Basically, each monad has its own implementation of the bind function. And you can write a bind function such that it does hoopy things between execution steps. For example:
If each step returns a success/failure indicator, you can have bind execute the next step only if the previous one succeeded. In this way, a failing step aborts the whole sequence "automatically", without any conditional testing from you. (The Failure Monad .)
Extending this idea, you can implement "exceptions". (The Error Monad or Exception Monad .) Because you're defining them yourself rather than it being a language feature, you can define how they work. (Eg, maybe you want to ignore the first two exceptions and only abort when a third exception is thrown.)
You can make each step return multiple results, and have the bind function loop over them, feeding each one into the next step for you. In this way, you don't have to keep writing loops all over the place when dealing with multiple results. The bind function "automatically" does all that for you. (The List Monad .)
As well as passing a "result" from one step to another, you can have the bind function pass extra data around as well. This data now doesn't show up in your source code, but you can still access it from anywhere, without having to manually pass it to every function. (The Reader Monad .)
You can make it so that the "extra data" can be replaced. This allows you to simulate destructive updates, without actually doing destructive updates. (The State Monad and its cousin the Writer Monad .)
Because you're only simulating destructive updates, you can trivially do things that would be impossible with real destructive updates. For example, you can undo the last update, or revert to an older version.
You can make a monad where calculations can be paused, so you can pause your program, go in and tinker with internal state data, and then resume it.
You can implement "continuations" as a monad. This allows you to break people's minds!
All of this and more is possible with monads. Of course, all of this is also perfectly possible without monads too. It's just drastically easier using monads.
Actually, contrary to common understanding of Monads, they have nothing to do with state. Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it.
For example, you can create a type to wrap another one, in Haskell:
data Wrapped a = Wrap a
To wrap stuff we define
return :: a -> Wrapped a
return x = Wrap x
To perform operations without unwrapping, say you have a function f :: a -> b
, then you can do this to lift that function to act on wrapped values:
fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)
That's about all there is to understand. However, it turns out that there is a more general function to do this lifting, which is bind
:
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x
bind
can do a bit more than fmap
, but not vice versa. Actually, fmap
can be defined only in terms of bind
and return
. So, when defining a monad.. you give its type (here it was Wrapped a
) and then say how its return
and bind
operations work.
The cool thing is that this turns out to be such a general pattern that it pops up all over the place, encapsulating state in a pure way is only one of them.
For a good article on how monads can be used to introduce functional dependencies and thus control order of evaluation, like it is used in Haskell's IO monad, check out IO Inside.
As for understanding monads, don't worry too much about it. Read about them what you find interesting and don't worry if you don't understand right away. Then just diving in a language like Haskell is the way to go. Monads are one of these things where understanding trickles into your brain by practice, one day you just suddenly realize you understand them.
链接地址: http://www.djcxy.com/p/47662.html下一篇: 什么是monad?