Hacker News new | past | comments | ask | show | jobs | submit login
The Delimited Continuation Monad in JavaScript (2014) (mattbierner.com)
45 points by akkartik on Feb 5, 2018 | hide | past | favorite | 14 comments



Every time I see someone discussing Monads in JavaScript without _mentioning_ Promises, I pause a bit. To my eyes, aside from a bit of type-juggling which makes Haskell programmers break out in cold sweats, Promises and Monads are similar enough.

The main difference is that .bind()/.then() implicitly converts (a -> b) to (a -> M b) - i.e. if the function you pass to .bind()/.then() does not return a Promise/Monad as it should, it's converted. Promise.create() does something similar. This level of type-juggling is not weird for a dynamically-typed language.

So my question is: rather than attempting to define your own Monads that are more type-strict than the language itself, how many of the behaviours in this article can be implemented by extending Promise.prototype instead?

    Promise.prototype.binary = function (right, binaryFn) {
        return this.then(leftVal =>
            Promise.resolve(right).then(rightVal =>
                binaryFn(leftVal, rightVal)
            )
        );
    };

    onePromise.binary(twoPromise, (a, b) => a + b); // Adds two promises together


I had a big problem with the automatic conversion on return values. This is like saying that Promise<Promise<String>> can't exist, only Promise<String>. This reminds me of when Perl disallowed Array<Array<String>>, insisting that only Array<String> was necessary.

The second drawback to this autoconversion approach is that it enforces a very specific runtime behavior, and requires drawing a line for compliant/non-compliant libraries. I remember reading the github discussion threads marveling at how they were at once dividing the community and crippling such an important feature.


You mean this thread? https://github.com/promises-aplus/promises-spec/issues/94

I love reading the for-against monadic promises. It actually is educating just following the discussion.


Yep, that's the one. I think it should be required reading.

Most of the folks in that thread are highly intelligent, and mean well. But to hear them toss out category theory as "impractical" and "not based in reality"?... It's just so cringeworthy. Here's their final spec, which seems absolutely convoluted to me: https://promisesaplus.com/#the-promise-resolution-procedure

Why does any of this matter to non-js devs (like me)? Because some of us want to target javascript with higher level languages, and externing a js promise instance is a necessity. The runtime-driven behavior described here means that the compiler will never really know the type of a given promise resolution. So, the compiler is very limited in what it can handle.


There can be some value in collapsible types, so long as there is a delimiter you can throw in. For instance, you could return a promise in a one-element array.

For Perl (which I don't know) I could imagine the argument went that joining arrays was common enough that having to call an explicit function would get in the way of this common case. Hopefully there was a reasonable way to box up the inner arrays to prevent splicing.

(I've been thinking about this sort of thing recently because I've been looking into J, where an array of arrays in a higher-rank array, which is important because of the way arrays are automatically split up for, say, element-wise operations. Sure enough, J has a delimiter that they call a box, so an array of boxes of arrays is not a rank-2 array.)


Nah, adding allocations isn't a good idea. The goal of a compiler is to do less work and fewer allocations.

The js promise instance is going to be some sort of extern type with special constraints. I imagine most compile-to-js languages roll their own monad-based promise types, and then provide some sort of conversion mechanism in special situations.


Maybe that's the goal of a compiler (though I'm not sure I agree), but that, to me, is somewhat subservient to language design and expressivity.

You can't really dismiss a language feature because an imagined implementation of the compiler would cause lots of small allocations. Just as one concrete example, Python has a special pool of tuples to optimize the case of iterating over key/value pairs in a dictionary (and likely only one tuple is used per for loop). Java does something similar-ish by relying on an Eden space in its generational garbage collector.

In Javascript, if it were the case small delimiter objects were used for promises and they proved to be a performance problem, I can imagine a couple of ways the interpreter could optimize that object out.


That sort of works, in the http://blog.sigfpe.com/2008/12/mother-of-all-monads.html sense. Promises are an awkward encoding of continuations, and so they can encode all monadic structures.

It sort of misses the point, though in a way which Javascript is incapable of expressing. Namely, that the principle of least power is a good thing, and you should restrict yourself to contexts which make only the operations you want accessible.


Promises and monads are way different though. Promises are used for "simplifying" asynchronous coding in imperative languages, and monads are a general computational methodology towards writing syntactically imperative-looking code in purely functional languages, built on top of type-classes. Without understanding why Haskell is purely functional, you couldn't even understand the motivation behind monads. And sure, you can extend a javascript prototype (or any object) to do something monad like, but without all of Haskell's strictness, you'd miss the jest.


> monads are a general computational methodology towards writing syntactically imperative-looking code in purely functional languages

I think you are conflating "monadic do-syntax" and larger concept of monad (in Haskell and elsewhere).

Many constructs in Haskell (moands/comonads/applicatives/free/cofree etc.) are just it, constructs (drawn from theory), which happen to have interesting properties. And people keep finding interesting applications for them. Some of those applications are tested with time and hence are more familiar (f.x. effect ordering with monads).


the only real issue with promises not following monadic laws is that once you try to compose various monads, you have to rethink all of the properties because you can't just fall back to any document about monads ever and hope that things will work like that.

It's kind of like implementing all of the gang of 4 design patterns but with some small differences. Kinds of make the whole language pointless.

In term of just getting stuff kind of working, yeah, they work fine. I worked on a library that had to do monad-like composition in extremely limited space, but promises were available to me, so I just built everything on top of those to fake it with as few bytes as possible. Pain in the ass to reason around and debug, but it worked.


I would run away and never look back if I saw someone using delimited continuations without types to ensure that values and continuations match up at all call sites. There is no trickier control flow.


We all know that we can Just Write The Tests


Somehow Schemers manage. Or maybe I'm just thinking of Oleg Kiselyov http://okmij.org/ftp/continuations/




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: