6 ms·
This is missing the one thing that all other "let me explain monads to you" articles is missing: a real example of what problems monads solve. As far as I know,
by freework 14y ago
This is missing the one thing that all other "let me explain monads to you" articles is missing: a real example of what problems monads solve. As far as I know, a monad is some kind of mathematical construct that is very useful when solving mathematical proofs, but has no use for the every day programmer. The examples in these kinds of articles are always completely abstract. You can very easily describe a database by using the "customer/phone number" example, yet I've never seen such an example with monads.
- dons 14y agohttp://www.haskell.org/haskellwiki/Monad#Interesting_monads http://www.haskell.org/haskellwiki/Monad#Interesting_monads Nothing to do with proofs. Monads are a generic interface -- an API -- for changing up the computational rules of your programming language. Just think about that for a second -- its a big deal. Maybe you want to work with non-deterministic threads, or with continuations, or quantum computations, or backtracking search, or exceptions or partial results, or some other notion of computation. But you want to still write in your usual language. So you embed that computational style as a monad, and now you have a really nice API for programming in that different environment -- inside your everyday language. Its simple, but profound. With (good support for) monads you aren't constrained to the default computational rules of your programming language anymore. You are free.
- lhnz 14y agoThe problem with this description is that I don't know what kind of benefits I can get from using different computational rules... Can you show me?
- dons 14y agoThe standard example is adding checked exceptions. You can refactor nested checks for null into straight line code in the Either (or Maybe or Option) monad, that does the same thing. Consider this pseudo/Haskell code. Functions might fail, so we have to check the return value using nested switches: modify s f = do ev <- get s case ev of Left e -> return (Left e) Right v -> do let u = f v ev <- set s u case ev of Left e -> return (Left e) Right __-> return (Right (v,u)) Lots of nested checks -- makes the code more unreadable, and harder to maintain. Equivalent to null checks in other languages. The 'case' stuff however, is the monadic 'semicolon' between each statement. So we can refactor to: modify s f = do get s >>= \v -> do let u = f v set s u >>= \v -> do return (v,u) Using the error monad definition of bind / >>= - http://hackage.haskell.org/packages/archive/category-extras/latest/doc/html/src/Control-Monad-Either.html#EitherT http://hackage.haskell.org/packages/archive/category-extras/... So then, if you language supports monadic syntax, you can actually use semicolon instead of 'bind': modify s f = do { v <- get s ; let u = f v ; v <- set s u ; return (v,u) ; } and if you are in a whitespace-sensitive language, the error checking disappears into the newline: modify s f = do v <- get s let u = f v v <- set s u return (v,u) So the above code, in the Either monad, is equivalent to the original code, however, we now have shorter, cleaner code, and a compile-time guarantee that all return values are checked. This example is my "programmable semicolon" motivator, from http://donsbot.files.wordpress.com/2009/01/semicolon.pdf http://donsbot.files.wordpress.com/2009/01/semicolon.pdf I chose the computational environment I wanted my code to run in -- one with checked errors -- and did this by overriding my language's default 'semicolon' behavior using the monad interface to do so. I reprogrammed my programming language, to make it possible to write better code. That's real power.
- loeg 14y agoIt seems like a trade-off: obvious control flow and some extra `if intermediate_result is None: return BogusValue` error checks are replaced by non-obvious control flow. Is the only value that you save a few lines of error-checking? It just doesn't seem worth it to me.
- dons 14y agoI think your arguing against abstraction in general. We replaced a manual, verbose and error-prone pattern with a standardized abstraction, and added in compile-time guarantees that the pattern is enforced for free. This made the code shorter and clearer, and exposed the actual logic of the code. Now imagine this over 100s of thousands of lines of code, often using different patterns. It is an enormous win at scale. Arguing that monads obscure control flow is strange - since most abstractions we care about obscure control flow - whether functions, loops or monadic bind - that's the point, after all... I don't write my own stack push/enter/pop to call functions, and nor should I have to write out all the other boilerplate control flow patterns I use, when it can be simply programmed into a monad, and left to the compiler.
- loeg 14y ago> I think your (sic) arguing against abstractions in general No. > We replaced a manual, verbose and error-prone pattern with a standardized abstraction Alternatively, we replaced some easy to follow procedural code with a manual, verbose, and error-prone abstraction. I have no idea what is going on with the author's decorated classes, generators, etc. How do you create a new monad pattern and debug it? All code is imperfect; it feels like we're just shifting some of the logic around to a different place. > since most abstractions we care about obscure control flow - whether functions, loops or monadic bind This is apples and oranges. Functions and loops are straight-forward, monadic bind (in this framework) involves indirecting through a bunch of complicated Python magic. > I don't write my own stack push/enter/pop to call functions, and nor should I have to write out all the other boilerplate control flow patterns I use, when it can be simply programmed into a monad, and left to the compiler. It sounds like an arguments from the exceptions camp — "everything would be so much better if we didn't have to check error returns from every subroutine!" But like exceptions over error codes, I think introducing monads is inventing a bigger problem to solve a comparatively small problem. Just my 2¢.
- frio 14y agodons' explanation will be better/more rigorous than mine, but in a more familiar syntax (Python), let's say you've got some functions: def get_user(id): # returns None if no such user exists return Database.Users.get(id) def send_message(user): user.send(message) def main(): user = get_user(1) # uhoh -- this could be None and we didn't handle it! send_message(user) So, we add a None check: def main(): user = get_user(1) if user != None: send_message(user) But... what if we want to log the status of that send_message call? def send_message(user): try: user.send(message) return None except Exception, e: return "Failed to send message; {0}".format(e) (Note that this is a contrived example, please don't critique the general dumbness ;)). Now... def main() user = get_user(1) if user != None: response = send_message(user) if response != None: log(response) As you can see, it's getting a bit hairy. But, what if we had a class that dealt with the Nones for us? This is what a monad is: an interface, that many different instances implement, which provides some behavior. The following only deals with single-argument functions, but: def NoneMonad(Monad): def __init__(self, arg): self.arg = arg def bind(self, func): if arg != None: next_arg = func(arg) return NoneMonad(next_arg) else: return NoneMonad(None) Now, we... def main(self): NoneMonad(1).bind(get_user) .bind(send_message) .bind(log) And, boom, the Nones are handled. Real, non-contrived Monads do more (and implement other behaviours -- like in dons' example, the Either response, or the Maybe monad, etc.), but hopefully this demonstrates the strength. Haskell's typically used as the example language, because there's syntax sugar in Haskell for making dealing with Monads prettier :). Having finally grokked them, it surprised me how simple the concept is. I think for people (like me!) coming from Java, Python, etc. who've not had to deal with the concept, there's this sort of imbued myth that they're a much more difficult concept than they really are. It's as simple as: a Monad is an interface, that can be implemented, that provides a construct for executing code in. Please forgive any blinding mistakes I've made in my code.
- tbatterii 14y agothis made more sense to me than the article. thanks.
- lolcraft 14y agocomplexFn = do x <- getSomeData y <- computeWith x z <- computeSomeMore y return z Let's say you have here straightforward imperative code. Of course, a real example would be more complex. But suppose x is now a list and you want to vectorise computeWith. Put your x in a box/monad, let's call it Parallel. Define return and bind to be parallel (bind = par . fmap, or something), and that's it. Transparent parallelism. Maybe it does numerical algorithms, and you want x to be an interval, or a tuple (value, error). Again, define the corresponding monad. Same thing with non-deterministic computation, amb-like operators, passing state between calls... The code that calls them stays the same. It also provides type safety for operations. For instance, some Haskell libraries allow programmers to specify a network protocol with monads, then write servers/clients that are statically checked to be correct. All sorts of things like that. Conceptually, a monad is a box with a tag on it (the name of the monad), and your rules for (1) putting things into it, and (2) applying functions to what's inside. Plus compile-time guarantees that you can't do weird things with them, like mix tags and stuff.
- andrewcooke 14y agowell, it's not claiming to explain monads. but it does contain a pile of examples. try looking at the mailbox example and then running the code. you'll have a pretty powerful impression of the way that monads can modify apparent control flow semantics.
- dbaupp 14y agoIt's explicitly not explaining what a monad is, first paragraph of the article: I've been informed that I didn't clearly explain what monads are and what the code examples are supposed to do. Sorry. I guess I assumed too much preexposure to monads. If you're no familiar with them, there are already so many tutorials on monads that I'll simply direct you to two of my favorites: spacesuits and wikipedia. I've also added more explanations of what the code snippets are supposed to do. I hope that helps. (The spacesuits link is dead (live[1]) and neither directly address "your motivating example" point, although the non-monadic vs monadic code comparisons demonstrate that monadic code can be much much shorter and less repetitive.) [1]: http://web.archive.org/web/20081206204420/http://www.loria.fr/~kow/monads/index.html http://web.archive.org/web/20081206204420/http://www.loria.f...
- Evbn 14y agoThe spacesuit one has been explicitly disowned by its author. "Programmable semicolons" is the rough modern consensus on the best one-line metaphor.
- Strilanc 14y agoI give an example in "How would I even use a Monad (in C#)?" ( http://twistedoakstudios.com/blog/Post867_how-would-i-even-use-a-monad-in-c http://twistedoakstudios.com/blog/Post867_how-would-i-even-u... ), assuming a hypothetical extension to the language: ///<summary>Pulls the monad type outside of the list type by binding across the list's elements.</summary> ///<remarks>Not actually possible to compile in C# today.</remarks> public static M<IEnumerable<T>> BindAll<M, T>(this IEnumerable<M<T>> sequence) where M : Monad { //<-- Type parameter M takes a type parameter T. Not currently possible. return sequence.Aggregate( M.Return(Enumerable.Empty<T>()), //<-- Static interface method. Not currently possible. (wrappedAccumulator, wrappedNextItem) => from accumulator in wrappedAccumulator from item in wrappedNextItem select accumulator.Concat(new[] { item }).ToArray().AsEnumerable()); } This single method combines several apparently distinct methods: waiting for all tasks, choosing all of the combinations from a list of lists, etc.
- ufo 14y ago> a real example of what problems monads solve Have you ever used an async promise library in Javascript? Its a monad! (you have a function to create a new "async promise" given a sync value and you have a method to chain a promise with a callback that returns a new promise) The complicated thing about monads is that they are an abstract interface. Having the abstract interface is good if you want to write generic code that works on all monads (and if you want to prove things about your code) but makes it hard to understand stuff. Another problem with monads is that its API can't be implemented directly in most languages so they get restricted to the more functional languages: one of the methods (>>=) receives an anonymous function as an argument (and many languages don't support those)and another method (the awkwardly named "return") is polymorfhic on its return value (OO languages can only handle polymorphism on the first input argument, the "this").
- mrcrassic 14y agoI think one of the best and clearest examples of a useful monad is the Error monad in OCaml (and Haskell, I think). Error is a type that can return two different things depending on the result of a computation. One could use exceptions for this sort of thing, but this is much, much cheaper.