where are they necessary?
The other day I was talking about functional programming - especially Haskell with some Java/Scala guys and they asked me what are Monads and where are they necessary.
Well the definition and examples were not that hard - Maybe Monad
, IO Monad
, State Monad
etc., so everyone was, at least partially, ok with me saying Monads are a good thing.
But where are Monads necessary - Maybe
can be avoided via magic values like -1
in the setting of Integer
, or ""
in the setting of String
. I have written a game without the State
Monad, which is not nice at all but beginners do that.
So my question: Where are Monads necessary ? - and cannot be avoided at all. (And no confusion - I like Monads and use them, I just want to know).
EDIT
I think I have to clarify that I do not think using "Magic Values" is a good solution, but a lot of programmers use them, especially in low level languages as C or in SHell scrips where an error is often implied by returning -1
.
It was already clear to me that not using monads isn't a good idea. Abstraction is often very helpful, but also complicated to get, hence many a people struggle with the concept of monads.
The very core of my question was if it was possible to do for example IO
, without a monad and still being pure and functional. I knew it would be tedious and painful to put a known good solution aside, as well as lighting a fire with flint and tinder instead of using a lighter.
The article @Antal SZ refers to is great you could have invented monads, I skimmed over it, and will definitely read it when I have more time. The more revealing answer is hidden in the comment with the blog post referred to by @Antal SZ i remember the time before monads, which was the stuff I was looking for when I asked the question.
I don't think you ever need monads. They're just a pattern that shows up naturally when you're working with certain kinds of function. The best explanation of this point of view that I've ever seen is Dan Piponi (sigfpe)'s excellent blog post "You Could Have Invented Monads! (And Maybe You Already Have.)", which this answer is inspired by.
You say you wrote a game without using the state monad. What did it look like? There's a good chance you ended up working with functions with types that looked something like openChest :: Player -> Location -> (Item,Player)
(which opens a chest, maybe damages the player with a trap, and returns the found item). Once you need to combine those, you can either do so manually ( let (item,player') = openChest player loc ; (x,player'') = func2 player' y in ...
) or reimplement the state monad's >>=
operator.
Or suppose that we're working in a language with hash maps/associative arrays, and we're not working with monads. We need to look up a few items and work with them; maybe we're trying to send a message between two users.
send username1 username2 = {
user1 = users[username1]
user2 = users[username2]
sendMessage user1 user2 messageBody
}
But wait, this won't work; username1
and username2
might be missing, in which case they'll be nil
or -1
or something instead of the desired value. Or maybe looking up a key in an associative array returns a value of type Maybe a
, so this will even be a type error. Instead, we've got to write something like
send username1 username2 = {
user1 = users[username1]
if (user1 == nil) return
user2 = users[username2]
if (user2 == nil) return
sendMessage user1 user2 messageBody
}
Or, using Maybe
,
send username1 username2 =
case users[username1] of
Just user1 -> case users[username2] of
Just user2 -> Just $ sendMessage user1 user2 messageBody
Nothing -> Nothing
Nothing -> Nothing
Ick! This is messy and overly nested. So we define some sort of function which combines possibly-failing actions. Maybe something like
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= Just x = f x
f >>= Nothing = Nothing
So you can write
send username1 username2 =
users[username1] >>= $ user1 ->
users[username2] >>= $ user2 ->
Just (sendMessage user1 user2 messageBody)
If you really didn't want to use Maybe
, then you could implement
f >>= x = if x == nil then nil else f x
The same principle applies.
Really, though, I recommend reading "You Could Have Invented Monads!" It's where I got this intuition for monads, and explains it better and in more detail. Monads arise naturally when working with certain types. Sometimes you make that structure explicit and sometimes you don't, but just because you're refraining from it doesn't mean it's not there. You never need to use monads in the sense that you don't need to work with that specific structure, but often it's a natural thing to do. And recognizing the common pattern, here as in many other things, can allow you to write some nicely general code.
(Also, as the second example I used shows, note that you've thrown the baby out with the bathwater by replacing Maybe
with magic values. Just because Maybe
is a monad doesn't mean you have to use it like one; lists are also monads, as are functions (of the form r ->
), but you don't propose getting rid of them! :-))
I could take the phrase "where is/are X necessary and unavoidable?" where X is anything at all in computing; what would be the point?
Instead, I think it's more valuable to ask, "what value do/does X provide?"".
And the most basic answer is that most X 's in computing provide a useful abstraction that makes it easier, less tedious, and less error-prone to put code together.
Okay, but you don't need the abstraction, right? I mean, I could just type out a little code by hand that does the same thing, right? Yeah, of course, it's all just a bunch of 0's and 1's, so let's see who can write an XML parser faster, me using Java/Haskell/C or you with a Turing machine.
Re monads: since monads typically deal with effectful computations, this abstraction is most useful when composing effectful functions.
I take issue with your "magic values Maybe monad". That approach offers a very different abstraction to the programmer, and is less safe, more tedious, and more error prone to deal with than an actual Maybe
monad. Also, reading such code, the programmer's intent would be less clear. In other words, it misses the whole point of real monads, which is to provide an abstraction.
I'd also like to note that monads are not fundamental to Haskell:
do
-notation is simply syntactic sugar, and can be entirely replaced by >>=
and >>
without any loss of expressiveness
they (and their combinators, such as join
, >>=
, mapM
, etc.) can be written in Haskell
they can be written in any language that supports higher-order functions, or even in Java using objects. So if you had to work with a Lisp that didn't have monads, you could implement them in that Lisp yourself without too much trouble
Because monad types return an answer of the same type, implementations of that monad type can enforce & preserve semantics. Then, in your code, you can chain operations with that type & let it enforce its rules, regardless of the type(s) it contained.
For example, the Optional class in Java 8 enforces the rule that the contained value is either present & non-null, or else not present. As long as you are using the Optional class, with or without using flatMap, you are wrapping that rule around the contained data type. No one can cheat or forget and add a value=null with present=true.
So declaring outside the code that -1 will be a sentinel value and mean such-and-such is fine, but you are still reliant on yourself and the other people working in the code to honor that semantic. If a new guy comes on board and starts using -1000000 to mean the same thing, then the semantics need to be enforced outside the code (perhaps with a lead pipe?) rather than through code mechanisms.
So rather than having to apply some rule consistently in your program, you can trust the monad to preserve that rule (or other semantics) -- over arbitrary types.
In this way, you can extend functionality of types by wrapping semantics around them, instead of, say, adding an "isPresent" to every type in your code base.
The presence of the numerous monadic utility types points to the fact that this mechanism of wrapping types with semantics is a pretty useful trick. If you have your own semantics that you'd like to add, you can do that by writing your own class using the monad pattern, and then inject strings or floats or ints or whatever into it.
But the short answer is that monads are a nice way to wrap common types in a fluent or chain-able container to add rules and usage without having to fuss with the implementation of the underlying types.
链接地址: http://www.djcxy.com/p/47668.html上一篇: Monad没有包装值?
下一篇: 他们在哪里需要?