6 ms·
Introduction to monads in JavaScript
- crazygringo 14y agoCan anyone tell me, in what real-world practical situation, this would be a better design model than just normal IO-based logging? Would there ever be a reason to do this in the browser? Or is this something that would be more useful in a Node.js environment, and if so, when? Or is it more theoretical than practically useful?
- gridaphobe 14y agoThe Writer Monad probably isn't too useful in any impure language, and IMO was a poor choice by the author. If you read the last few paragraphs though, you're introduced to the List Monad (the bit about HTML nodes), which is useful in impure languages. Another example I would have liked to see is the Maybe Monad, which allows composition of functions that might return a value, or null/undefined.
- tmhedberg 14y agoIf we look at an impure language like JavaScript through Haskell-colored glasses, essentially every line of code in a JS program is automatically in the IO monad. Haskell's bonus is that it gives you the frequently-exercised option of writing code that's explicitly not capable of doing I/O, simply by omitting IO from its type, and having this constraint enforced by the compiler. This self-imposed restriction helps avoid the numerous problems that come along with uncontrolled side effects. But since you can't "escape" the IO monad in JavaScript, there's not much point in making it explicit by doing output in a visibly monadic way. That being said, there are plenty of uses for the monad pattern outside of the I/O domain. That's where a lot of emphasis is placed in Haskell, because Haskell couldn't do I/O without monads, but there are tons of other useful monads that have nothing to do with side effects. As the article illustrates, two of these are Writer and List (written as [] in Haskell). Another massively useful one is Maybe, which represents nullability, but by making it explicit in a value's type we can avoid a lot of the problems that arise in "nullable by default" languages. In some sense, jQuery is an example of a monadic library, wherein the unit function is $, which lifts its argument into the jQuery monad, and bind is represented by the chain of method calls applied to that lifted value, each of which returns a different value, still wrapped in the jQuery monad. I've been told that LINQ in the C# world works similarly (in fact, there is supposedly a lot of bleed-over of ideas from Haskell into .NET because Simon Peyton-Jones works alongside some of the .NET language designers at Microsoft Research).
- jlarocco 14y agoThis is one of the reasons why I don't use Haskell. Every concept you mention is made more complicated thinking about it with monads. Every time I use Haskell I feel like too much mental energy is wasted on silly stuff like this.
- zaptheimpaler 14y agoDisclaimer: I've never used Haskell Yeah, although the article mentions how monads eliminate some boilerplate code, the whole concept of monads is lots of boilerplate code that doesn't exist in other languages. I don't really see the point of writing something like a web server or anything that interfaces a lot with users in Haskell. Seems like its strengths are manipulating data and parallelism, but it would make more sense to use a wrapper around it to handle I/O.
- numeromancer 14y agoMonads are important in providing well-defined semantics for code. They are a way of taking imperative code and making it functional; the semantics of functional computation are much easier to define than those of imperative code. If you were interested in proving your program correct with a deductive proof (rather than, say, statistically acceptable with tests) , monads would be very important to you. They weren't designed to provide useful design patterns. That's why articles attempting to demonstrate them as useful design patterns often seem artificial. But I think that as the art of software engineering progresses, the problems they solve will become more important.
- tmhedberg 14y agoLet's say I have several functions I want to compose, feeding the result of one as the argument to the next. But each of these functions has some failure condition, which, if it occurs, causes the function to be unable to return a normal result. In an imperative language, I might represent that failure by returning a null reference, and I'd write something like this: fooResult = foo(x); if (fooResult == null) { return null; } else { barResult = bar(fooResult); if (barResult == null) { return null; } else { bazResult = baz(barResult); if (bazResult == null) { return null; } else { quuxResult = quux(bazResult); if (quuxResult == null) { return null; } else { return quuxResult; } } } } I think that's a pretty common pattern: call a function and check whether it succeeded. If it did succeed, then pass its result to the next function and repeat. If it failed, then propagate the failure outwards, skipping the rest of the computation. But that's pretty tedious and repetitive code to write. The signal to noise ratio is incredibly low. Not only that, but if I get lazy or forgetful and don't check for null in any one of those cases, my code could blow up at runtime. It would be nice if we could abstract out the pattern of error checking and propagation of errors so we didn't have to type so much boilerplate around the real meat of the computation, which is just the four function calls. Haskell's Maybe type serves precisely this purpose. The exact same code as above, in Haskell using the Maybe monad, would be: return x >>= foo >>= bar >>= baz >>= quux That's it! Error propagation will happen automatically, and if no errors occur along the way, we'll get the same result at the end. Now, you might think that there must be a lot going on behind the scenes to make that one-liner actually do what we want it to do. But there isn't really. Maybe comes for free in the standard libraries, and even if it didn't, we could trivially define it ourselves like this: data Maybe a = Nothing | Just a instance Monad Maybe where return = Just Nothing >>= _ = Nothing Just x >>= f = f x That's the entire definition of the Maybe data type and its complete error propagation behavior. There's nothing more to it than that. And not only will it work in the specific example I gave, but in any situation where I have a chain of functions to call that might fail in some way. After writing that once (or not at all, since it's in the libraries), I can now apply that pattern anywhere I need it, in any program, to concisely express what took around 20 lines of onerous and error-prone repetition without the benefit of the monad. That's how monads eliminate boilerplate, and one of the reasons why you might benefit from understanding them even if you're not a Haskell user.
- ufo 14y agoThe problem with monads is that its hard to justify the whole concept until you start wanting abstract some code to work with more then one monad. That said, specific monads actually show up pretty often. A very important example is the Promise monad used by some Javascript async libraries: (for example, see https://github.com/kriskowal/q https://github.com/kriskowal/q or http://dojotoolkit.org/documentation/tutorials/1.6/promises/ http://dojotoolkit.org/documentation/tutorials/1.6/promises/) Basically, creating a new promise is the monadic "return" and the "promise.then" method is the monadic "bind" (or "fmap", if the callback returns a normal value instead). I found that knowing a bit about monads helped me work better with the promises from the Dojo framework but I guess your mileage may vary...
- Peaker 14y agoGeneralizing Monad in a language without return-type polymorphism is hard. How do you implement "return" (unit)?
- tikhonj 14y agoThe best way would be to specify the return type directly. This is unfortunate, but it is how basically any language (that I know) save Haskell would do it. So instead of having a return function, you would use something like List.return or Maybe.return or whatever. This is the same way you would solve the problem of read (that is, the opposite of toString): Double.parse, Integer.parse and so on. When you have a polymorphic function, you would pass in the type as appropriate. Clearly, this is not ideal. However, this is basically what the Haskell compiler does for you with typeclasses--loosely speaking, a function polymorphic over a typeclass gets transformed into a function accepting an instance and its normal arguments. In OCaml, you could solve this problem with a functor. So your polymorphic module is parametrized on another module that provides the return and bind functions. I personally like this approach less than Haskell's, but it is more flexible (for better or worse): you can have different modules passed in for the same type. In Haskell, to get different behavior for the same type using typeclasses, you'd have to create a new type with newtype. So in short, it isn't impossible: you just have to do more manually than you do in Haskell.
- tikhonj 14y agoThe problem is that the tutorial was transliterated from Haskell. So while it's useful for understanding the idea behind monads, the examples are not very motivating in a language like JavaScript. The one interesting example in the article for actual JavaScript use is probably the list monad which lets you compose functions returning an arbitrary number of results rather than just one (these functions are often called "nondeterministic"). However, it's possible to imagine other useful monads for JavaScript use. For example, I think we can abstract over CPS (continuation-passing style, like asynchronous functions in Node.js) functions using a monad. This will involve changing our idea of CPS a little bit: instead of having a function f(x, k) where x is the argument and k is the callback, let's have a function f(x) which returns a function g(k) with x and k being the argument and callback respectively. Now we have to define the basic monad functions on it. First, let's start with unit (which is called "return" in Haskell). The idea is simple: we want to go from some value to a function accepting a callback. What is a reasonable way to do this? Let's define it in a trivial way: we go from x to a function that, given a callback, immediately calls it with x. So: CPS.unit = function (x) { return function (k) { k(x); }; }; Next, we want to define bind. Now, what will bind do? The core idea is this: given a wrapped value m x and a function f: x → m y, we need to apply f to the wrapped value. Now, in this context, the "wrapped value" is a function accepting a callback. Essentially, this wrapped value is a promise to provide x at some point in the future. So we want to take this "promise" and get a new promise for the result of calling another CPS function on it. Here is a way to do this: CPS.bind = function (f, x) { // f: x → m y, x: m x return function (k) { x(function (x_val) { f(x_val)(k); }); }; }; Admittedly, this code is a little confusing; I will try to explain it. The basic idea is simple: we create a new promise (the outermost returned function) that will trigger after x triggers and will then pass both the result of x (x_val) and the given callback (k) into f. That is, we get a promise of the result of f called with the eventual value of x. This lets us bind together all these asynchronous values so that the result is still asynchronous. This makes managing "callback hell" simpler: instead of having a ton of nested callbacks, we can have a string of calls to bind. We also get a bunch of functions for free. For example, let's say you have a deferred x and want to apply a function to the result. This is essentially mapping the function over the deferred value. You could write it like this: CPS.map = function (f, x) { return CPS.bind(compose(CPS.unit, f), x); } However, the true power of monads is that this map function works exactly the same way on every monad. So you could rewrite it like this: Monad.prototype.map = function (f, x) { return this.bind(compose(this.unit, f), x); } then make all your monad "types" (for lack of a better word) inherit from Monad. So the CPS type I showed here would get map for free, but so would an analogous List type. Moreover, this is true for a whole bunch of useful functions. For example, you would be able to generalize reduce to accept a reducer function that behaves like a monad instead of like a normal function (you could call it reduceM). So you would then be able to reduce using an asynchronous function! I think that's pretty cool. Note that it isn't enough just to write unit and bind to get a monad--the functions also have to follow some laws. If these laws aren't followed, you get all sorts of weird behavior with your monad. I think my CPS instance follows these laws, but I'm too lazy to check just this moment. I'll leave it as an exercise to the reader (I've always wanted to say that). The reason we have to do some extra legwork (like creating the CPS object) is that JavaScript is not good at differentiating between types. Particularly, there is no way to consider only CPS functions. Additionally, there is a valid monad instance for all functions which behaves differently from the CPS one, and there would be no way to differentiate between the two without making it explicit. Now, I'm sure there is a neat way to make bind and unit methods of appropriate types, but that would make the examples even more complicated and I don't want to think about it right now :P. In summary: monads let you generalize over a bunch of different concepts, not just IO. In particular, you can use monads to write code that works generically over CPS functions and nondeterministic functions and a bunch of others. EDIT: I forgot to note: promises already have a well-known meaning in JavaScript. I don't actually know that meaning because I've never used them. The only reason I use the word is because I couldn't think of anything better to call them. It's likely the semantics of existing promises and the semantics of the objects I described are actually different, but they do serve the same purpose.
- ibotty 14y agore the comment in the original article about monadic javascript: take a look at roy. http://roy.brianmckenna.org http://roy.brianmckenna.org
- plainOldText 14y agoWow, I now understand monads. This article nailed it.
- tikhonj 14y agoIf you like this sort of thing, check out the Arrowlets[1] library for JavaScript. It uses "arrows", an abstraction in a similar vein to monads, to make CPS functions and event handling neater (among other things). [1]: http://www.cs.umd.edu/projects/PL/arrowlets/ http://www.cs.umd.edu/projects/PL/arrowlets/ From playing around with it, I think the arrows from that library are more useful to practical JavaScript programming than monads.
- Ralith 14y agoConsidering that arrows are a generalization of monads, that shouldn't be a surprise.
- luciferous 14y agoSince you're familiar with arrowlets, would you mind having a look at samsara? [samsara]: http://lcfrs.org/samsara/docs/dragndrop.html http://lcfrs.org/samsara/docs/dragndrop.html
- tikhonj 14y agoThat seems cool--CoffeeScript's syntax is definitely better-suited for this sort of thing than JavaScript's! The main page says the library's in CoffeeScript, but the drag and drop example uses JavaScript. Do you have a similar (or even the same) example in CoffeeScript? I'd like to see what it looks like.
- luciferous 14y agoThere are two parts to example: the plain functions, and the composition. CoffeeScript can make the plain functions look different, but the composition is the strangest looking; I need to find a better syntax for representing a directed graph in code, and CoffeeScript doesn't help with that part.
- doublerebel 14y ago...it helps you spot accidental complexity...Being able to spot and extract such boilerplate can radically improve the clarity of your code. I think a second version of this tutorial in CoffeeScript would really shine a light on how CS is the best of JavaScript. Monads and FP are a breeze -- practically encouraged -- in CoffeeScript. It really removes the cruft of JS syntax and lets me focus on well-designed code, and I've found myself using similar patterns since I switched to CS. I code faster and (contrary to what I expected) debugging is much easier.
- tripzilch 14y agoI've read many attempts to explain Monads by now, but this is the first time I think I actually "got" it. Well done!