promises-aplus / promises-spec

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.

Home Page:https://promisesaplus.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Incorporate monads and category theory

paulmillr opened this issue · comments

Brian Mckenna criticised current spec. He proposes to use FP approach to achieve much better modularity.

Suggest to read it, really good ideas with just three changes.

http://brianmckenna.org/blog/category_theory_promisesaplus

His proposal is to incorporate into spec three simple apis:

  1. Promise.of(a) will turn anything into promise.
  2. Promise#then(f) should take one function, not two.
  3. Promise#onRejected(f): move onRejected to prototype instead of second arg.

edit: see promises-aplus/constructor-spec#24 for more discussion

Yes, I really would like monadic promises as well…

Only I fear that won't happen. I already objected in #75 on the recursive assimilation process, but as it stands the then method is overloaded with join as well :-(

And that point function is subject of the (independent) resolvers-spec. As Brian already wrote, it would be sufficient in most cases to construct a simple thenable, instead of using some implementation-dependent Promise constructor.

Yeah this is really not happening. It totally ignores reality in favor of typed-language fantasy land, making a more awkward and less useful API just to satisfy some peoples' aesthetic preferences that aren't even applicable to JavaScript. It misses the point of promises (modeling synchronous control flow from imperative languages), albeit in a novel way from the usual misunderstandings.

It is also hilariously inaccurate, as the thenable described comes nowhere near satisfying the spec. My guess is that it would pass approximately one of the ~500 tests in our test suite.

Someone more diplomatic than me should probably chime in too.

@paulmillr, @bergus: I fully understand and appreciate this perspective, and similar ideas have been discussed at various lengths by the Promises/A+ contributors.

As @domenic has rightly pointed out, though, it isn't practical for Promises/A+ to go in the direction Brian Mckenna is proposing.

@domenic you miss the point of monads. They implement exactly what you're describing in the post. They create sequential, synchronous control flow. That's why they're used in Haskell as IO! That's why promises are monadic!

Please point out where I'm missing the point of promises.

I'm basing the API off of category theory but it's for my aesthetic preferences!? When you say the API is "more awkward and less useful", that's reflecting your aesthetic preferences. Mine haven't even come into it!

I'm going to work on a separate specification - I'm hoping the rest of the JavaScript community will embrace it, rather than immediately reject it because it was based off of types.

I'm going to work on a separate specification

@pufuwozu terrible idea. The reason why A+ isn't separating map and bind isn't because of aesthetic preferences. We didn't even discuss the issue because it's a breaking change and it came way too late into the discussion. then became a de facto standard that even the DOM is now likely to follow with DOMFuture.

@juandopazo I don't really care about having bind fall back to map when it doesn't return a promise. I'd prefer it to throw an exception but I'll just be careful.

What I do care about is a specified way to create promises and onRejected to be separate from then. That's all.

A specified way to create promises is coming, it's just not here yet. see https://github.com/promises-aplus/resolvers-spec, which is nearly ready for a version 0.

Separating .then and .onRejected was not done for very good reasons:

  1. It allows for the parallel control flow of: Do Operation A, If operation A succeeded, do B, if operation A failed, do C. Separation makes that very hard to write.
  2. Keeping the API surface that needed to be implemented for interoperability to a minimum is critical to the success of promises. There are are many promises out there that don't come close to implementing this spec, but are still good enough for full interoperability because they have a .then method that takes two arguments: onFulfilled and onRejected.

What I do care about is a specified way to create promises and onRejected to be separate from then. That's all.

@pufuwozu In that case, I would suggest you should join the discussion for creating promises rather than go out on your own. You could also propose a discussion to this group for an optional onRejected API (several promise implementations provide this already: when.js's promise.otherwise, Q's promise.catch, other libs provide promise.fail, etc.).

However, changing then simply is not an option.

While it might be nice for some people to have a specified way of creating promises, it's certainly unnecessary. Different platforms will have different ways of creating them. This is already the case. For instance, see the WinJS promises vs DOMFuture. The point of having a spec is compatibility between them. And in that spirit, having a smaller API surface is better.

Creating a new specification just to add onRejected sounds a bit overkill.

Finally, the reason we didn't separate out bind and map is simply that it's not pragmatic to do so. It fits very nice with a theoretical computer science model of the world, but not with practical usage of the libraries. JavaScript is characterized by pragmatism over theory, and is ultimately devoid of type safety as a result. Without the benefits of type safety, separating out bind and map doesn't gain you anything.

It's not the aesthetics that @domenic or me or @briancavalier or anyone else like that matter; it's the aesthetics of the language itself. JavaScript's concept of a promise is subtly different to (although closely related to) the idea of a monad. It's the result of people attempting to use the ideas of a monad in real world applications, not the result of ignoring past work and coming up with something new from scratch.

@ForbesLindesay I don't care very much about the separation of bind and map. As long as it's at least bind then I'll just be careful with the other behaviour.

... ideas of a monad in real world applications...

That's exactly what I want. Let's not implement partial monads that we can't abstract over. Let's recognise it for what it is and allow functions which work for ALL monads. Why not allow DRY code instead of some strange idea of aesthetics of JavaScript?

It might be too late but I'd rather a specification which allows DRY code with very partial backwards compatibility. I do not accept aesthetics of JavaScript as an excuse for developers to write more libraries that we can't abstract over.

We can abstract over them. There are hundreds of JavaScript libraries that use the promises and next to none that use traditional monad APIs.

You completely (deliberately?) missed the point of what I was saying about monads in real world applications. I'm saying people took the ideas of monads and used them in real world applications and adapted them to fit well with how JavaScript works. They are not, nor are they designed to be, interoperable with monads from Haskell. If you're creating a bridge for interop between the two languages, you could always automate that conversion.

They are very demonstrably DRY and possible to abstract over. If anything the .then method allows for more DRY code because you don't have to special case for whether the result is a promise or a non-promise value. Functions like Q.all demonstrate that they are a sufficiently powerful abstraction.

This is beginning to look like trolling, and if it continues to look like trolling, I will just be muting this thread. Without any interest int he aesthetics of JavaScript, I don't see your competing spec as anything likely to cause a problem.

I don't see how you can't abstract over promises. You showed exactly how in your post. If we followed the DOMFuture API, it would look like this:

// the DOMFuture way of defining point()
function point(value) {
  return new Future(function (resolver) {
    resolver.accept(value);
  });
}
// Adding onRejected
Future.prototype.onRejected = function (f) {
  return this.then(null, f);
};

// flatMap is exactly the same if you don't want to separate bind and map
function flatMap(p, f) {
    return p.then(f);
}

Starting a standards war over this sounds like a great way to use our time.

People misunderstood what I meant by "abstract over" - of course we can build libraries on top of the promises specification :)

I want to abstract over all monads. That's a very important thing for DRY code. I should be able to write code like this for example:

function liftA2(pa, pb, f) {
    return ap(pb, map(pa, function(a) {
        return function(b) {
            return f(a, b);
        };
    }));
}

// Promises (called when both succeed)
liftA2(readFIle('hello.txt'), readFile('world.txt'), function(hello, world) {
    console.log(hello + ' ' + world);
});

// Optional values (only inserted into database when both exist)
liftA2(optionalUsername, optionalPassword, function(username, password) {
    insertIntoTable("user", {username: username, password: password});
});

// Arrays (function called for each element of each array)
liftA2([1, 2, 3], [4, 5, 6], function(a, b) {
    console.log(a + b);
});

All I need to make the above work is for optional values and arrays to have a then and each constructor to have a point function. I don't have to write that function for each different monad - I only have to write it once. That's just one example of a function that works for any monad - there's many, many more.

I do care about the aesthetics for the API. I don't not accept aesthetics of JavaScript as an excuse to not allow me to do the above.

I care very much about generalised, DRY code. It's the right thing to do, even in JavaScript.

It allows for the parallel control flow of: Do Operation A, If operation A succeeded, do B, if operation A failed, do C. Separation makes that very hard to write.

It's not just hard, it's impossible to get the same semantics. Promise has to be a bifunctor (i.e. has a bimap that takes two functions) for this to work at all. There are other bifunctors out there, such as Either, with either as its bimap.

@pufuwozu: I think the reason you're getting so much pushback on this, is that we create Promise APIs to make it easier for developers to write and manage asynchronous code. From my own experimentation in this area, you can only take the formalization so far in Javascript before the language starts to push back. The fmap/bind divide is one of those places; it's just much easier to use the library if you combine the two together.

You recall, perhaps, how electromagnetism and the weak force are actually part of the same mechanism, but only at very high temperatures? Well, we simply can't turn up the furnace that high in Javascript. Certain formalisms work beautifully in Haskell but fall flat in Javascript without support from the language. Haskell's static typing lets you offload a ton of work onto the compiler, but Javascript doesn't help much at all!

Prime example: in my own sandbox I've attempted to split Promise into two parts: a Result wrapping of try-catch, and a Future for deferred return values. It's theoretically nice until you realize one thing: monads don't naïvely compose. I don't know how the heck you would write monad transformers in Javascript. 😀

@Twisol JavaScript is limited but we can still achieve DRY code by applying category theory. I do it: http://bilby.brianmckenna.org/ - the "but real world" excuse doesn't apply; I've used that library in a compiler!

Different monads don't compose but monad transformers are easy to implement in JS when you get to them ;)

@pufuwozu can you show with a concrete example why you can't write the example code you posted with promises?

Your example of code that would be difficult to write with promises as they currently stand would in fact be easy to write for promises. I'm not 100% sure of the behavior of ap and map since you haven't included source code for them, but those two functions would (obviously) be different for promises vs. traditional monads. Since there are vastly more libraries using promises than monads, it is the monads that are preventing you from writing DRY code.

If you provide JavaScript source for ap and map using monads, I will write the corresponding functions for promises. Otherwise, stop trolling.

+1 to not starting a standards war. Any correct model of promises can be wrapped into a monadic interface, and it is pretty normal for standards to be a little off and require wrapping with a library.

Also, our understanding will improve over time, and it would be a disaster to have to move as slow as standards processes and have to wait for social trends to fall in line with mathematical truths. For example, before 2008 @pufuwozu would probably not have known to mention applicative functors.

-1 to the theory-phobia and accusations of trolling. Whether you like it or not, promises form a monad, and @pufuwozu is advocating that the standard expose this very useful structure. I understand why it will not, and agree somewhat with the decision, but that does not reduce the validity of the criticism.

@ForbesLindesay you should take a look at my blog post:

function map(p, f) {
    return flatMap(p, function(a) {
        return point(f(a));
    });
}

function ap(p, pf) {
    return flatMap(pf, function(f) {
        return map(p, f);
    });
}

So we need a both a point and flatMap which works for Arrays, optional values, promises and anything else monadic. Then we can derive lots of useful DRY functions, like the liftA2 function that I posted above.

There will be no specified way to do this. That's what my blog post should have shown.

Thanks @kennknowles.

Leaving aside the issue of the spec and whether or not we should change it (my opinion: we shouldn't), I'd be interested in seeing a pure approach to Promises. Something doesn't have to conform to a spec to be useful, and if it turned out that we're all wrong - that you can write pure code without it being brittle in the face of dynamic typing - then there's no reason inspiration couldn't be taken from the approach.

I don't want to see a standards war or any division of effort; just understand that there are reason that the spec is the way it is, and we need more than an impassioned plea for purity to make changes.

To clarify:

I'm not anti type theory per say. I actually find it extremely interesting. I've written a fair bit of code in languages like ML and F# that do a decent job of strong typing systems. I also think it would be an interesting experiment to attempt to create a strongly typed subset of JavaScript.

This is not the place for such experiments though, and my accusations of trolling are not directed at @paulmillr who made the very reasonable comment that we should consider category theory and monads more extensively. My accusation of trolling was leveled at @pufuwozu because he repeatedly tells us that the promises spec as it stands is insufficient for writing certain functions in a DRY way, yet refuses to demonstrate any actual problem.

@ForbesLindesay I demonstrated the problem in my blog post. There is absolutely no refusal. It's an easily demonstrable problem.

I don't think there's any need to fear a "standards war." @pufuwozu can do his own thing, and I'm sure he'll have fun doing so, but I imagine the proportion of people using "monadises" versus Promises/A+ will be roughly equal to the proportion using Roy over ECMAScript.

The point function in your blog post is still fantastically broken.

The problem is really simple.

Let's take three monads. Maybe, Promise and Array. I think we can all agree they support a point operation that takes a value and returns a monad and support a flatMap operation.

Now if I wanted to write a SINGLE polymorphic function that took two monads and did an operation on them. It would need a mechanism to look at my arguments and extract both a point and flatMap operation in a standardized way.

This isn't about writing DRY and composable functions with promises. It's about doing it with monads and accepting that a promise is one of hundreds of data types and tools in our toolbox. Sure we can just drink the promise coolaid and do everything with promises but that defeats the point of composability. Composability is trivial in a ************.

@pufuwozu a reasonable suggestion is to write a flatMap and point function which just has a massive switch in the body that handles all real monads and all toy monads.

@domenic that's fine. I usually hang out with the people that "do the right thing" - though, I actually thought my blog post would be taken seriously and appreciated.

@ForbesLindesay yeah, it's not easy to write a minimal implementation. Will I see a real one when you've managed implement the above?

@Raynos a switch would make the assumption that the world is closed and we could never write a new monad or use a new library. I would much prefer:

  • Promise.point = function() { /* ... */ };
  • Promise.prototype.constructor = Promise;

Which would allow us to approximate point for most functions.

Yep, the problem @pufuwozu is having is just that promises don't interoperate with monads. We already have a function that's broadly equivallent to how your blog post describes liftA2. We normally call it all and it takes promises, values, or arrays of promises/values in any combination and returns a promise for an array. You can see it implemented here.

To build "liftA2" on top of that:

function liftA2(arg, fn) {
  return all(arg).then(function (res) {
    return res.map(fn);
  });
}

Here is a minimal promise implementation. Most of the additional code is to handle the guarantees of asynchronous behaviour and polymorphism on return types.

As for the criticism of switches, you don't switch on library, you switch on type of object: e.g. promise, monad, array...

As for your desire to have something like Promise.point, it is being considered for the Resolvers Spec in promises-aplus/constructor-spec#5

facepalm

You folks really need to learn to communicate this issue in terms that anyone can understand. And I mean anyone who doesn't know category theory or how Haskell works.

PS: here's the working code of the original post: http://jsbin.com/iroxeg/1/

@ForbesLindesay that only works with promises. I have to write that function for every different monad. That is not DRY!

You can't switch on everything in the whole world! If I write a library that uses a point which switches on each type in each library it uses, I can't add another. I have to reimplement each of those functions. That is not DRY!

@juandopazo now make it work for Arrays, optional values, promises and a to be decided structure (I have one up my sleeve).

That is what I want from the specification. I'm not sure how this message is being missed. How can I make it clearer?

@pufuwozu: don't worry, there are lurkers here who certainly get the point, agree with you, and don't contribute because it couldn't possibly be made any clearer 😉.

You're not going to get through to people in this manner if it hasn't yet happened.

@pufuwozu write a monads module. test for flatmap and point. then whitelist all the uncompliant monads. if someone writes a new uncompliant monads, update the module, put it in the whitelist. bump the patch version and republish.

also thank you. I finally understand the point of monads. however polymorphism in JavaScript is a lost cause.

How can I make it clearer?

You can make it clearer by showing concrete examples of things that break, can't be done or would be much easier some other way. With code.

For instance, nowhere in your post does it say exactly what you expect point to do. Only after reading @Raynos' comment I got the idea that it's not just a wrapper for creating a promise for a certain value, but that maybe you expect it to have a different {{class}} depending on the value you want to wrap.

You want to add value to the a standard we have agreed on, I'd say the burden of explaining how it's supposed to work for "Arrays, optional values, promises and a to be decided structure" is on you.

You're not going to get through to people like this if it hasn't yet happened

I'm sincerely offended. I am trying to get something of value here and so far I haven't. Both this attitude and the troll accusations aren't helping.

@juandopazo: I changed "like this" to "in this manner" because it was ambiguous and I feared people may have taken it like that. Unfortunately, you did. I did not intend to call you and others "people like this".

@juandopazo: I just call it construct, as in a constructor, since it's more obvious what it's trying to do. It isn't standard nomenclature, but whatever. 😀

You want to add value to the a standard we have agreed on, I'd say the burden of explaining how it's supposed to work for "Arrays, optional values, promises and a to be decided structure" is on you.

I agree. I think there's value to be had, but Promises/A+ is at best a single kind of monad. If you want broad application of monads, there needs to be a separate discussion and exploration on what the best way to do it is. @pufuwozu's bilby library shows one possible way (and it's certainly very interesting!), but I'm not convinced that it's the only way, nor the simplest.

@juandopazo

The above will work for any monad. List of lists? Optional optional value? If the JavaScript community settled on using flatMap as a method, we could write DRY, generalised code for monads.

He does state in the blog article that it's about polymorphism across all the monads, not just promises. Is there something here that's not obvious?

I'm not sure which parts aren't clear so here we go. A 3 function unspecification of Promises/M(onadic):

  • A promise must provide a then function. It takes a function which itself will return a promise. The then function returns a promise and schedules the given function. When the given function is resolved, it should sync state with the promise returned by then.
  • A promise must provide a constructor which has a point method. The point method will create a new promise, containing the given value.
  • A promise must provide a onReject function which will handle rejections for the next then call.

Done. The second and third part are my additions. Now it's become a superset of "the monad specification". What does that let me do? This:

function map(p, f) {
    return p.then(function(a) {
        return p.constructor.point(f(a));
    });
}

function ap(p, pf) {
    return pf.then(function(f) {
        return map(p, f);
    });
}

function liftA2(pa, pb, f) {
    return ap(pb, map(pa, function(a) {
        return function(b) {
            return f(a, b);
        };
    }));
}

Now I can add a then and point to Array.prototype and Array, respectively. Or I could write a wrapper around Array, if I don't want to mutate the constructor or prototype. I can also do that to my optional value wrapper. Or any other monad that I want. I can also do it after I've defined the above function.

// Promises (called when both succeed)
liftA2(readFIle('hello.txt'), readFile('world.txt'), function(hello, world) {
    console.log(hello + ' ' + world);
});

// Optional values (only inserted into database when both exist)
liftA2(optionalUsername, optionalPassword, function(username, password) {
    insertIntoTable("user", {username: username, password: password});
});

// Arrays (function called for each element of each array)
liftA2([1, 2, 3], [4, 5, 6], function(a, b) {
    console.log(a + b);
});

So: I just want two specified functions which make it a monad. That is all. I promise 😃

Does the above make it any clearer?

A promise must provide a onReject function which will handle rejections for the next then call.

This is asymmetric solely for the purposes of getting rid of a combination method. You can achieve the same power by specifying a single branch primitive that takes both callbacks, and implementing then and onReject from it.

It also seems like you're neglecting the failure path (i.e. functions chained on through onReject). We really want analogous versions of liftA2, ap, and map for both sides.

@Twisol monads do somewhat gloss over the failure path. Failure path methods are provided separately. Which path is that monadic path does come down to aesthetics - which one is your "main" path? If failures are your "main" path then you will need to write a wrapper to flip it. Bad luck for that 0.000001% of cases?

Hang on, I apologize if this is a stupid question, but...do people really prefer the overloaded then for aesthetic reasons?

I'm scratching my head trying to figure out why we want a method whose purpose in life is to take two other functions and then call one of them. Isn't that just an unnecessarily convoluted way to implement two methods? I'm pretty sure if I came across that in my code, my first instinct would be to refactor it into two methods.

Can someone explain this to me?

He does state in the blog article that it's about polymorphism across all the monads, not just promises. Is there something here that's not obvious?

Maybe it's that I just caught a cold and I'm a bit slow. Sorry if that's the case.

Does the above make it any clearer?

Yes, a lot. Thank you.

Ok, I see two issues then. The first one is how to create monads/promises of the same type of another one. That's a fair question to ask and it's something we have already discussed to an extent. It is something we're approaching with the new Promise(fn) signature. This ensures that new Promise(fn) instanceof Promise and helps with creating flavors of promises. For instance an Array promise would look like this:

function ArrayPromise() {
  Promise.apply(this, arguments);
}
ArrayPromise.prototype = Object.create(Promise.prototype);
ArrayPromise.prototype.constructor = ArrayPromise;

ArrayPromise.prototype.filter = function (fn, thisp) {
  return this.then(function (array) {
    return array.filter(fn, thisp);
  });
};

Getting a new promise from another one is just a matter of doing new promise.constructor(fn). Following that idea, I think point was discussed with the name Promise.from. I think you make a good point for why it may be important to standardize how to construct promises. Unfortunately, as I mentioned before, we already have dissimilar implementations (see WinJS and DOMFuture), so it'll be a challenge.

The constructor.point function probably has some details that can be discussed better in the issue that Raynoes opened.

The changes to then are the hard ones to justify. You ask for then to only accept functions that return promises. I agree that separate methods would be easier to follow and probably less complex to maintain. However, that's something I think we can agree that it's way too late to change. You yourself concede:

I don't really care about having bind fall back to map when it doesn't return a promise

This leaves then taking only one argument and having a separate onRejected method. And I honestly can't see the reason. then taking two arguments is really useful because of the default behavior of the second argument. When it's not provided, it's just chained to the next then call, allowing you to listen to failures in only one place. On top of that, then(callback, errback) looks like just a superset of then(callback) which you could ignore for inter-operability with other monad-like objects. And onRejected could just be a wrapper for then(null, fn).

I am so happy that everyone's on the same page now 😄.

This has been a fascinating and entertaining thread! The best one in
promises-aplus so far!

I am fully in favor of seeing the bright minds here do the thought
experiment to see if something more monad-like can replace promises -- even
in Javascript.

Might I suggest that we pursue this as a thing to follow the ratification
of 1.1?

-- J

On Wed, Apr 10, 2013 at 4:23 PM, Michael Ficarra
notifications@github.comwrote:

I am so happy that everyone's on the same page now [image: 😄].


Reply to this email directly or view it on GitHubhttps://github.com//issues/94#issuecomment-16199525
.

@juandopazo

The first one is how to create monads/promises of the same type of another one.

It's not that we want to create an ArrayPromise that inherits from Promise. It's that we want to create a List thing that is a "monad" meaning it has a flatMap and point operator of some kind.

We can consider then as a being a superset of flatMap.

We want to write higher order functions on things that look like Functors (it has a map function), Monads (it has a flatMap and point operator) and other such constructs.

For example

function double(input) {
  return input.flatMap(function (x) {
    return input.constructor.point(x * 2)
  }) 
}

function PromiseMonad(p) {
  p.flatMap = p.then
  p.constructor.point = Promise.from
  return p
}

function ListMonad(arr) {
  return { 
    flatMap: function (f) {
      return [].concat.apply([], arr.map(f)
    },
    constructor: { point: function (x) {
      return [x]
    } } 
  }
}

We've now written double function that works on both a list of numbers and a promise for a number. We can extend it to also work on a stream of numbers and work on a function that takes a callback and returns a number and etc etc etc.

The thing to take away is not "monads are better then promises" it's that the .then semantics on an ArrayPromise probably make more sense if the onFulfilled function was called multiple times with every single value in the array and that the array returned from the onFulfilled function were concatted together to create a new ArrayPromise.

Now of course this breaks the then semantics so it makes more sense to just deal with this as an abstract monad with an abstract flatMap operation.

On top of that, then(callback, errback) looks like just a superset of then(callback) which you could ignore for inter-operability with other monad-like objects.

Sort of. It is possible to exploit the function's arity (via f.length) to make partial function application more normal. In Haskell, if you have f :: a -> b, then f a :: b. However, in Javascript, f.bind(undefined, a) :: () -> b. If you check f.length (and make assumptions about varargs), you can make something that behaves more normally. I suspect this would be important for implementing ap.

@juandopazo awesome.

As you notice, then is just an overloaded bind/flatMap. Great.

I'm happy to discuss point/from in resolvers-spec.

So handling errors will just require users to do then(null, fn) - that's awful but I'll live with it.

Sounds like it should be possible to make an implementation that is both Promises/A+ compliant and also compliant with what I'm proposing.

Thanks.

I'll chime in. I really have no idea about category theorey and the term monad
is scary. I really didn't get it till I started looking at f# computation
expressions then it just clicked.

Monads are really just a set of callbacks for an interpreter. The language
that is interpreted can look imperative if you choose so.

F# has taken this to it's logical conclusion with computation expressions.
http://msdn.microsoft.com/en-us/library/dd233182.aspx, an imperative embedded
language where you the developer can define what each language keyword
means via callbacks ( Monad methods )

Why is this good. Well functional programming is good but often you
ended in nesting hell. Hence all the requests for async / await type
keywords to flatten out the nesting in javascript.

computation expressions are a generalization to all kinds of
flattening problems introduced by functional programming.

So in the context of this discussion making sure all your monad thingies have
all the correct standard interfaces makes it possible to do things like
computation expressions

So for example in F# you can do

open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "http://www.microsoft.com/" 
                "MSDN", "http://msdn.microsoft.com/" 
                "Bing", "http://www.bing.com"
              ]

let fetchAsync(name, url:string) =
    async { 
        try 
            let uri = new System.Uri(url)
            let webClient = new WebClient()

            // IMPORTANT BIT HERE. The code after the let!
            // executes asynchronously. Even the exception
            // handing is handled asynchronously :)

            let! html = webClient.AsyncDownloadString(uri)
            printfn "Read %d characters for %s" html.Length name
        with
            | ex -> printfn "%s" (ex.Message);
    }

let runAll() =
    urlList
    |> Seq.map fetchAsync
    |> Async.Parallel 
    |> Async.RunSynchronously
    |> ignore

runAll()

Note that the async keyword is just telling the compiler/interpreter
that in this scope the monad interpreter will use the async workflow

There are callback interfaces for most of the standard f#
keywords even loops and exception handling.

So in summary don't get scared off by terms like Monads
and Categorey theory. Think of Monads as callbacks for an interpreter
for an imperative language where you can define what each
keyword in this language does. Everybody agrees on the
language and everyone is free to implement any callbacks
they like.

But to get it all to work you need to agree on a common
set of interfaces.

Thanks to the Promises/A+ folks for asking really good questions, and thanks to @pufuwozu (and others) for answering with lots of good information and examples. I'm basically in favor of anything that makes promises "better" and more useful, without also making them less approachable/learnable.

My main concerns are:

Approachability/Learnability - it's been the experience of many Promises/A+ contributors that Promises, in general, already are daunting to developers. It's at least been my experience that Monads, in general, can also be daunting to folks. I think that we need to be careful not create something that is even harder for developers to grok/use. As a super simple example, I fear that the name point is not intuitive for what it does, and would confuse many developers. It's great that @pufuwozu has said over in #24 that he's cool with one of our previously proposed names from. If monadic promises can be theoretically strong, but also no more difficult to learn, I think that kind of synergy of theory and practicality can be a big win.

Interoperability - there's a substantial and growing amount of deployed code based on non-promise thenables, Promises/A, and Promises/A+. I think we have to consider that, and make sure we don't break the world. For example, changes to the signature and/or behavior of then could be a big barrier adoption, or worse, could cause developers some serious headaches. Again, it seems like it's possible to move forward in a way that maintains compatibility/interoperability, but it's something we need to keep in mind as we explore this approach.

Promises/A+ 1.1 timing - This version is effectively "done", and I think we should release it. We can continue to explore monadic promises for a subsequent version.

DOM Futures - It's not clear to me how this could/would/should influence the current DOM Futures work, but maintaining compatibility seems very important. I'm hoping someone more involved with that effort (@domenic, @erights, @wycats?) is able to comment.

It would also be good to hear from other lib authors, especially @kriskowal, @erights, @wycats, @lsmith, and @novemberborn.

commented

@briancavalier As far as approachability/learnability goes, I believe Monads provide a much simpler interface for Promises/A+ (even ignoring the "being more composable" argument, which is a rather strong one). I think we should value simplicity over a seemingly easy but complex API (plus we get more compositionality yay!). The current specification for then is really complex, it merges lots of concepts and deals with a fair bit of automatic conversion for the user. While this does allow a particular user "type less" in the general case, it's not something that really scales on the long run (see the Abstract Equality Operator).

People don't need to be concerned about what Monads are or aren't to use Promises. I think that's an important part of this discussion I don't recall people mentioning before. Users who don't get Category Theory, Monoids, Monads, Functors, Applicatives and all that stuff (which would include myself for now :3) can just look at this whole thing as more combinators. Functional idioms are beginning to spread throughout the JS community, even though people don't grok all the internal details of those idioms: function composition, generic and specialised folds, etc. So I think it's worth a shot working together to construct an API that is simple (by the means of separating and composing concerns instead of overloading things), and still uses a vocabulary that is slightly more familiar to JS developers to help with adoption.

Interoperability is a problem. For example, by changing the behaviour of then and making it simpler, we will have to ask all previous implementors to adapt to the new specification. Abstractions built on top of those might also need to some rework. However, sometimes breaking backwards compatibility can be worth it.

Just got reminded by @dherman - anything with a then is a "Thenable" which is treated specially in the spec.

If we also put a then on Array - things would seriously break when an array is returned from a function passed to promises libraries!

There's two choices:

  • Not everything with a then method can be treated as a promise Thenable
  • The then method shouldn't do special things when value returned from the function doesn't have a then - we can't have libraries rely on that behaviour

One of these things really needs to change to allow DRY code.

To be exhaustive, here's two more choices:

  • Let promise libraries break when we add things like Array.prototype.then
  • Don't write DRY code

I know some people will reach for that last option but I don't want to give up 😄

OK. So. People seem to not have understood how bad of a mismatch @pufuwozu's post was for Promises/A+, both in content and in tone. Let's try to clarify that, and hopefully make it clearer why I stand by my original post.

First, coming into a community and saying "you're doing it wrong! look over here!" in a post filled with technical inaccuracies and misunderstandings is rude and unwelcome. Compounded with well-known troll @paulmillr, of racist CoffeeScript pull-request fame, as the messenger, you can see how this is problematic.

Now, let's get down to brass tacks. Although @pufuwozu has backed off on his onFulfilled-must-return-a-single-type argument, the general perspective from which such a demand comes from is unrealistic, and indeed stems from a typed-language fantasy land where you can enforce such things and don't have to think about the possibility of receiving an unwelcome type. Indeed, his suggestion to leave returning non-promises unspecified is ridiculous for a JavaScript specification.

Furthermore, the problem isn't only a narrow typed-language worldview. It's a narrow pure-functional language worldview as well! Promises are an implementation of the monad pattern that helps to encapsulate an exception channel into the monad. That much is, I hope, obvious to all. But a key part of how promises implement this pattern is by embracing the imperative nature of the language, and automatically translating return (of non-thenables) and throw into the appropriate promise representation. Any implementation of monads that is "right" for JS will necessarily embrace these imperative possibilities, instead of ignoring them.

Finally, there seems to be a crucial misunderstanding of what the Promises/A+ spec is meant to do. It is not meant to dictate a standard for all aspects of promises in JavaScript, and it is certainly not meant to provide a standard for monads in JavaScript. Instead, it is something that grew out of many years of implementer experience, and the need to provide a minimum interoperable specification of the then method. As such much of its complexity is focused around interoperability (see the entire Promises Resolution Procedure). Remember:

A standard for sound, interoperable JavaScript promises—by implementers, for implementers.

This is why decisions like promise creation are not part of the core specification, while in contrast, we do carefully specify thenable assimilation.

I think the key here is something that grew out of many years of implementer experience, and more importantly many years of JavaScript experience. We have found an appropriate implementation of a specific monadic pattern in JavaScript, and that includes things like how we handle exceptions, how we handle polymorphic return values, and how we duck-type other implementations of the same pattern. This also feeds into discussions of flattening promise chains; it has previously been discussed over and over that a promise-for-a-thenable is a problematic pattern in JavaScript and should not be supported. (A promise for another monad, of course, is fine.)

In contrast, nobody has spent years implementing algebraic patterns in JavaScript. If they had, I think we'd be having a very different discussion right now, with much less focus around Haskell idioms and much more focus around those types of issues. The insistence of some people in this thread that the Haskell (or Scala, or...) implementations of these algebraic patterns is what we should do is very misguided.

I fully support any efforts to try to come up with some standard way of expressing monads and other algebraic concepts in JavaScript. Preferably, it should embrace the language instead of trying to make it conform to patterns of languages from other paradigms. But that's not something I am interested in spending time on as part of Promises/A+. I think a separate GitHub organization, perhaps with overlap from those who seem interested, is the right place to do such work.

In the meantime, Promises/A+ has no intention of incorporating whatever nascent "monad standard" such an organization comes up with. Neither, I think I can safely guess, does the DOM Standard and their DOM Futures.

Indeed, I think the standardize-first approach is the wrong one; it is certainly the opposite of what we have done with Promises/A+. Rather, I'd suggest going off and building libraries, with appropriate wrappers---one for Promises/A+ promises; one for arrays; one for an option type implementation; etc. If you have, after a few years of experience, managed to create a pattern that is widely used in libraries as popular as jQuery, Ember, Angular, WinJS, and more (perhaps with some flawed implementations, like jQuery's, lol): only then should you write up a spec, and a test suite. You need those years of experimentation to determine what the spec should even say.

Finally, after those years of experimentation, and the subsequent months of hammering out a standard, then come back to us. Maybe, if you've built an extremely compelling "algebraic JavaScript" ecosystem, Promises/A+ would consider a 2.0 revision in which we effectively tell all Promises/A+ implementations that the benefits are compelling enough that they should implement these new APIs as well. Most likely, if you're indeed that successful, most Promises/A+ implementations will already have adopted your patterns, and it'll just be a matter of codifying de-facto reality into something with all the edge cases smoothed out---like we have done with Promises/A+ 1.0 and 1.1.

commented

@domenic

it has previously been discussed over and over that a promise-for-a-thenable is a problematic pattern in JavaScript and should not be supported.

Except that the current behaviour of then, while allowing for compatibility with all sorts of previous work, is confusing, and rather prone to break exactly because of the overloading.

Someone returns a thing that has a then method and is fed into a Promise function? Broken. Someone use a Parser Combinator library that uses then for sequencing actions but no promises, feeds that into a promise-expecting function? Broken. How does the user reason about that? Well, he doesn't, and that's what is really terrible about Promises/A+.

Specially if Promises/A+ gain enough traction to be used everywhere as they should, consider:

var compileP = liftNode(compile) // compiles AST to something
var flattenP = liftNode(flatten) // flattens and optimises AST
var parseP = liftNode(parse) // parses text, returns AST
var source = fs.createReadStream('source.foo')
var compileSourceP = compose(compileP, flattenP, parseP)
spit(process.stdout, compileSourceP(slurp(source)))

Now, consider spit is a function of type spit :: Promise Stream, Promise String -> Promise (), and slurp is a function of type slurp :: Promise Stream -> Promise String. parse, flatten and compile are straight-forward in meaning, but they are not functions that know about promises, they are just wrapped in a function that transforms the arguments into a Promise, applies the function, and wraps the return in a Promise.

The problem now is all of those functions need to use then to wait for a promise to be resolved. Let's say the AST happens to have a then method for whatever reasons, this means we wouldn't be able to write that code, because flattenP would try to assimilate the return of parse, which is not a Promise.

See also @pufuwozu's comment above yours.

Is @pufuwozu filled with Haskell's dogmatism? I don't think so. Could he have worded his proposal better? Perhaps. Does he raise good points that we should evaluate? I think he totally does. Even if we disregard the whole "monads-category-theory-functional-programming-narrow-minded-pure-theoretical-ideas", there are some problems with the specs right now, which will mean things are gonna be fucked up as people begin to use them more and combine them with regular values. This is made worse by JS's lack of type constraints.

Indeed, I think the standardize-first approach is the wrong one; it is certainly the opposite of what we have done with Promises/A+.

It's also one of the things that bring in some terrible incidental complexity because now you have to support all of the previous work on this front. Same happened with ECMAScript. And is happening with ES6 again. Accidental complexity is never something we should strive for, imho.

As a side note, the tone of this discussion is really not helping. Fights means people get more defensive and less open to other people's ideas, which is, I believe, not what we want here(?).

@domenic my implementation of point is wrong - where are my other technical inaccuracies and misunderstandings? You haven't commented on them.

... and it is certainly not meant to provide a standard for monads in JavaScript.

It's not - but let's at least make it compatible! What was the reason not to?

Furthermore, the problem isn't only a narrow typed-language worldview. It's a narrow pure-functional language worldview as well! ...

That whole paragraph has nothing to do with what I've been talking about. The API that I derived is not pure at all. Thinking that this has anything to do with purity is absolutely wrong. I have no idea where you got that idea.

I think the key here is something that grew out of many years of implementer experience...

You can't just say "we've spent years implementing X, so it doesn't have to do/be Y" - that's absolutely useless. Give a reason, not an argument from authority.

... and more importantly many years of JavaScript experience.

I have been writing hobby JavaScript for 11 years - professionally for quite a few of those years. I write a few serious JavaScript libraries. I'm not coming in here from outside of the JavaScript community (not that it should matter, at all).

I also use many other languages - does that make your JavaScript experience more important or something?

We have found an appropriate implementation of a specific monadic pattern in JavaScript, and that includes things like how we handle exceptions, how we handle polymorphic return values, and how we duck-type other implementations of the same pattern.

They're not monadic - that's the whole point. What you're doing could be monadic - it's very close!

Preferably, it should embrace the language instead of trying to make it conform to patterns of languages from other paradigms.

Why do you have this crazy idea that because Haskell recoginises that monads are monads, then we're trying to make JavaScript like Haskell? You're being very dishonest.

The API that you can derive makes sense algebraically. This has nothing to do with Haskell or Scala.

In the meantime, Promises/A+ has no intention of incorporating whatever nascent "monad standard" such an organization comes up with.

Useless.

Finally, after those years of experimentation, and the subsequent months of hammering out a standard, then come back to us.

Wait, so is it absolutely no or maybe?

Come on. I read all of that for nothing - there's no content!

I have not seen any reasonable excuse against recognising that promises are monadic. You've basically come up with "it's pure" (incorrect), "it's from Haskell" (incorrect), "it's not from JavaScript" (that's what I'm trying to fix) and "implementors made this - we don't need your input" (urgh, thanks).

Anyway, I'm going to reiterate our 4 choices:

  1. Use something other than checking then
  2. Remove then which does special things for when returned with no then
  3. Let the promises that implement this spec break when used with DRY code
  4. Don't write DRY code

Yes, all the options suck. Problem is that I think #4 sucks the most.

@killdream the issue of things which have .then methods but aren't "thenables"[1] is vastly overstated. I haven't seen a single issue on GitHub about a bug resulting from this behavior. Given that I work on projects that use promises (both my own and other people's) almost every day, I would've seen hundreds if this was a significant problem. It just isn't.

[1] thenable: (something close enough to a promise to be successfully assimilated

@ForbesLindesay I added a then method to Array. Everything broke. Want to file an issue for that?

OK, I've now seen one person with a problem resulting from this, and you extended the prototype of a built-in. There are exactly 2 reasons why that would be acceptable:

  1. You're experimenting with an API you hope will be considered for a future version of JavaScript. What you've just demonstrated is that adding such a method would break lots of websites, so it won't be added. That concludes your experiment.
  2. You're shimming functionality that is already either in at least one JavaScript environment (browser or node.js) already or has been specified within a standards body with sufficient backing. The promisesA+ standards body doesn't yet carry that kind of weight, you most certainly don't.

@ForbesLindesay urgh, missing the point.

I just wrote a wrapper around Array and added a then method. Everything broke!

commented

@ForbesLindesay I'm okay with assimilating promises when you return a promise from a function in an on{Fulfilled,Rejected}, I'm not okay with assimilating things with a then function. That it hasn't happened until now doesn't mean it will never happen (potential ≠ guaranteed). For example, null is a terrible API design, everyone agrees with it, but even so null references are only "potential" problems.

Specially problematic with things that looks like thenables but aren't quite. Duck-typing is not the way to go here because the semantics of a Thenable are really important for them to be assimilated successfully, and these kind of errors might not exactly be early errors, which is more of a problem because now you have to step through your whole code base to figure out what went wrong. Dynamic branding would be a better option to mark compliant promises specifically and only assimilate those. Then the user has to go through all the trouble to wrap foreign things that are close to promises, which ensures they won't shoot themselves on the foot by a seemingly innocent code like the one in my example above.

@ForbesLindesay I wrote this:

function Id(a) {
    this.value = a;
}
Id.of = function(a) {
    return new Id(a);
};
Id.prototype.constructor = Id;
Id.prototype.then = function(f) {
    return f(this.value);
};

Everything broke!

@pufuwozu seems to be arguing that Promises/A+ ought to be compatible with monads because it only requires a small change and doesn't have any significant drawback.

@domenic seems to be arguing that the drawback is insurmountable, but I'm not seeing where this is demonstrated.

Indeed, his suggestion to leave returning non-promises unspecified is ridiculous for a JavaScript specification.

Why is this ridiculous? jQuery doesn't specify what happens when you pass a hundred arguments to $(foo).siblings - does it need to? JS is a dynamic language. There are all sorts of unspecified legal actions; as JS authors, we are used to that.

Compounded with well-known troll @paulmillr, of racist CoffeeScript pull-request fame, as the messenger, you can see how this is problematic.
I think the key here is something that grew out of many years of implementer experience, and more importantly many years of JavaScript experience.

Serious question: when people argue with you using ad hominem attacks and bludgeoning you with their credentials, does that make you more likely to think that (A) they are probably right after all, or (B) they are digging in their heels for pride rather than what's best?

Please reconsider.

@killdream "Let's say the AST happens to have a then method for whatever reasons," Don't do that.

The original E promise API which inspired all these others has no assimilation. When I first heard about assimilation, I thought it was a terrible idea, for many of the same reasons that underlie many of the above objections. However, as I saw the JS promise landscape evolve, I despaired more over a worse problem:

Several similar but different elephants in the room: jQuery promises, WinJS promises, Q promises, and DOMFutures. All of these but DOMFutures have enough installed base that they're not going away. And re DOMFutures, never underestimate the ability of the w3c to be an elephant even for premature "standards". Much code will have to be written in an environment inhabited by multiple such promise systems simultaneously. If none of these recognize each other's promises as anything promise-like, then programmers will face the burden of dealing with a "jQuery promise for a Q promise for a WinJS promise for a DOMFuture for a number". Call it the JQPFAQPFAWJPFADFFAN problem. I didn't see how we could get from that situation to one with an agreed standard promise anything.

Fortunately, the starting point for all of these elephants was so similar that the Promises/A+ process was able to tease out and codify a common-enough ground for all of these to agree on. This is messy real-world standards work; as much politics and sociology as technology and math. Given the constraints imposed by legacy, we should all be overjoyed that the Promises/A+ community has been able to extract something as beautiful as they did. But even if they all agreed on exactly the same spec, so long as jQuery promises only recognize jQuery promises as promises, and likewise for the others, we would still have the JQPFAQPFAWJPFADFFAN problem.

Had ES6 with its unique symbols happened long before any of these promise libraries, we could have used a unique "@then" symbol for assimilation and so avoided the accidental collision @killdream worries about above. Likewise, if all of these had stuck with the E (or original Q) name "when" rather than renaming it "then", we would have less accidental collision, and would be less irritating to category theorists. (FWIW, I think "when" was a much better term than "then" for this anyway. I am still unclear on the history of how this got renamed.) Alas, it didn't turn out that way.

Given multiple promise systems that each recognize only their own promises as promises, but which mostly agree on the meaning of "then", assimilation turns out to be a surprisingly pleasant way for these to co-exist. Given the actual situation we have to start from, having this assimilation be based on duck typing on the presence of a function named "then" is unfortunate, but there was no other practical choice.

@rtfeldman the comparison with jQuery is completely out of place here for two reasons:

  1. jQuery sucks at things like this, check out their broken promises.
  2. jQuery isn't a spec, so it's completely different.

A fair comparison would be the ECMA Script spec, which almost always specifies these corner cases (the only reason it would ever not do so would be by mistake.

@pufuwozu Your Id object:

function Id(a) {
    this.value = a;
}
Id.of = function(a) {
    return new Id(a);
};
Id.prototype.constructor = Id;
Id.prototype.then = function(f) {
    return f(this.value);
};

Looks plenty enough like a promise to me, when I assimilate it using a true Promises/A+ library I get a promise which resolves to the value of the Id. That's well specified, well defined behavior. I've found it helpful hundreds of times, and never found it a problem, and nor has anyone else.

@killdream

never happen (potential ≠ guaranteed)

Yes, but JavaScript is not a language that concerns itself greatly with proving things. Pragmatically speaking, I don't think it will cause anyone problems unless they are trying to abuse promises to make them more like Haskell Monads, as such, I don't think it's a problem. We considered "branding", but that ultimately causes the same problems, just one down the line: "It doesn't matter how many _s you add to the start of a property in JavaScript, it's still public".

@ForbesLindesay Fair point that jQuery isn't a spec.

Here's the ECMAScript 5.1 spec on Array.prototype.reduce: http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21

  1. If len is 0 and initialValue is not present, throw a TypeError exception.

This is the ECMAScript spec saying "if you pass an empty array and no starting value to reduce, that doesn't make sense, so TypeError."

Granted, explicitly specifying "throw a TypeError" is not the same thing as leaving it unspecified, but this is certainly an example of a JS spec saying "you can't use certain types in places where they don't make sense," no?

@rtfeldman effectively saying "you can't use certain types in places where they don't make sense," by deterministically throwing a TypeError is fine. Unspecified isn't. Care to rephrase what you had in mind in as a deterministic spec?

I'm not certain that I understand the problem here. As understood, the following are equivalent:

p.then(onFulfill, onRejected)

and

p.then(onfulfilled, undefined)
p.then(undefined, onRejected)

indeed, many implementations provide single-function shorthands. And of course, a full two-argument could be built from those single-function shorthands, thus

function then (onFulfill, onReject){
  p.shorthandFulfill(onFulfill)
  p.shorthandReject(onReject)
}

It would seem, then, that nothing in the Promise/A+ spec precludes the use of a promise as a monad, and the monadic promise proposal does not preclude the use of a promise as a practical notation for asychronous behavior. Given that Javascript and the spec provide that .then(oneCallback) operates the same under each proposal, the debate seems unnecessary.

Why choose?

The question is only how the notation for the spec should be accomplished. I agree with the overarching sentiment that we should describe the specification, so much as possible, using notation that users and developers understand. In this way, clients can read the spec and comprehend what is happening. Category theorists can use the spec trivially and without translation. And there is no reason why it can't be pointed out that distinct uses of the .then functions result in monadic behaviors.

The .then position is only one of a few pieces of the proposal, but I simply don't get this piece at all. I will write separately about the other issues.

commented

@ForbesLindesay @pufuwozu's example breaks depending on the assumptions the developer has when he wrote the Id constructor. If he doesn't expect it to be assimilated by a Promise library, it's broken.

@erights Legacy and incompatible things are unfortunate, which was I said all that stuff about accidental complexity. I believe explicitly wrapping non-conforming implementations would be best. Have a function like promiseFromThenable(a) :: Thenable a -> Promise a the developer can use to explicitly glue alien functions together, but I haven't really considered edge cases.

My example with the AST was based on the assumption I don't control that library. @polotek seems to have a Node library with a then method that isn't a promise. Promise-expecting functions can't use that library as a result.

@ForbesLindesay I actually think the probability of these kinds of things increase as you use Promises as actual Promises, because then you'll be mixing them with all the sorts of regular values, and it won't be apparent from your code base what is a promise and what isn't. If I omitted most of the definitions of my example, it would be one of those cases:

// is `source` a thenable? Is the result of `slurp? The result of `parse`? The result of `flatten`? ...
spit(process.stdout, compile(flatten(parse(slurp(source)))))

@polotek Please fix your Node library. Thanks in advance.

@killdream yes, until @polotek fixes his library, it can't co-exist with various promise libraries. It is far too late to do anything to fix that. The evolution of JavaScript has always been and remains constrained by its history of usage.

@wizardwerdna "p.then(onFulfill, onRejected)" evaluates to a promise for the value that the invoked callback will return. What does "p.then(onfulfilled, undefined); p.then(undefined, onRejected)" evaluate to?

@killdream

I use promises is in all sorts of large real world applications. Some of which are closed source, but there are some notable exceptions such as esdiscuss and the now deprecated component-website. You just don't end up suffering these issues.

As for your idea of forcing people to explicitly convert thenables into "Promises" it simply doesn't work. There is no sure fire way to tell the differences between a Promises/A+ promise and a thenable. Branding would get close, and was considered, but ultimately rejected. Search through the issues if you want to see more.

@wizardwerdna

There's a slight problem, you're neglecting the return value of then. p.then(onFulfilled, onRejected) is actually equivalent to:

(p will be fulfilled) ? p.then(onFulfilled, undefined) : p.then(undefined, onRejected)

Unfortunately p will be fulfilled is uncomputable

Another piece of brian's article goes to his "point" function. This functionality already exists in several libraries, typically named "coerce" and sometimes referred to as "assimilate". Indeed,the newest spec requires an "abstract procedure" called Promise Resolution Procedure, which is often manifest in code as resolve(thingie). This is nothing more than exposing resolve to the API. On the other hand, since it can easily be coded from the already-specified API, why does it matter?

Indeed, let me ask this question. Since the monadic use of Promises/A+ can be built completely from the spec, why not consider it simply to be a mixin that makes a Promise into an MPRomise?

erights, you got me. How about

p.then(onFulfilled, undefined).then(undefined, onFulfilled)

This returns a promise for the state and value that the invoked callback will return, albeit through a chain of promises via the Promise Resolution procedure.

That one is much more obviously different. Consider what happens if onFulfilled throws an exception.

The nonstarter in Brian's proposal, in my view, is the following:

I think then should only take the onFulfilled function and it should have unspecified behaviour if the function doesn't return a promise (for simplicity; map can be derived).

I simply don't think that we are all that hung up on types here in the land of javascript, and the /A+ specified behavior seems intuitive to me. Since Brian doesn't care (his definition permits the existing usages), the /A+ spec seems to do what he wants, just more.

@wizardwerdna well, it's tricky to explain.

If we have other things that have then because they're all monads - then adhearing to the promises spec promises that you'll be stuff when I pass a monadic Array in.

If the promises spec doesn't specify that it should fall back to map, then some libraries can reject that behaviour and give early errors.

It's not about types.

@ForbesLindesay

Perhaps I was unclear. Considering the chained expression, as compared to the two-parameter version, doesn't the return promise in each case end up rejected with the error of the onFulfilled call.

@wizardwerdna: In your chained example, if the onFulfilled callback fails, then it will be called again, but with an error instead of whatever was originally passed.

@pufuwozu Brian, trying to follow. Can you give an example? As understood, you can still wrap the old library behaviors with a coerce function.

Invariably, you will ordinarily try to use just one library anyway, resorting to other objects only when, for example, an I/O or Ajax library returns an ugly out-of-library "thenable". But its a trivial matter to use the coercion procedure for any return value, or better yet, to define a library-version of the external API calls, returning "legit" promises.

My point is that the present Spec seems multi-purpose, and the limitations on their use proposed by Brian seems easily built atop it. If I am not wrong, what's the beef?

@Twisol OK, I get it. The two-parameter version will only ever call the first callback, and the then-promise rejects with the error. The two-step version, errors out, so the then-promise rejects with the error, triggering the second callback. This is a semantically important consequence of the one-callback-only limitation of the spec.

Thus, I agree that we don't have equivalence, in that the monad can't seem to readily reproduce the A+/spec version. But doesn't it work fine the other way, in that the monadic version can be replicated by the spec? Thus, why the sadness in the monad community, since the spec already gives them what they want. Defining monadThen as function monadThen(cb){specThen(cb);} and monadOnRejected as function monadOnRejected(cb){specThen null, cb);}

@wizardwerdna so here's my "monad spec" for JavaScript:

A monadic value is something that:

  • then takes a function which takes a value and returns a monadic value of the same constructor - then also returns a monadic value of the same constructor
  • constructor.of creates a monadic value of the same constructor

It also has to obey these laws:

  • of(a).then(f) is the same as f(a)
  • m.then(of) is the same as m
  • m.then(f).then(g) is the same as m.then(function(x) { return f(x).then(g); })

Great, promises implements part of this specification (and hopefully more, soon). Awesome.

I make my Id constructor implement the above monadic specification - now I pass it to a promise library. Uh oh, it also has then so promises thinks it's a Thenable and does strange things with it. It's buggy.

It's not about promises not being part of "the monadic spec" - it's that promises will break when there's more than just promises involved.

Should I move forward with implementing many things with then and just let promises break? Or should promises change to not let this happen?

Does that make sense now?

@pufuwozu Don't define then methods that don't conform to the Promises/A+ spec. If you want a then-like method with a different behavior, call it something else.

@erights but Promises/A+ already provides a compatible then method. They are close to monadic. It's a problem with promises if they do strange things with other monads. They shouldn't.

commented

It's really sad that @erights highlights how tied we are to a stripped version of structural typing where every superset of { then :: Function } is considered a Promise =/

@wizardwerdna: Yes, that's right. Since the 2-ary then is strictly more powerful than then and otherwise, you can implement both of them in terms of the former. I do think that the 2-ary then should be called something else, but that ship sailed a long time ago.

The thing is, there are other things in Promises/A+ that you have to be careful of. Despite @pufuwozu's statement that "it's not about types", there are a lot of design tradeoffs based on the very fact that you can't differentiate types beyond a certain level of complexity. Have a single type you want to handle specially? Great, use instanceof. Have a whole range of types that have to implement an interface? That kind of check, in Javascript, ends up with problems.

And unless you want to use a transpiled Javascript language, there are no good solutions to the problem which also keep things at an approachable level. I admire @pufuwozu's approach to functional techniques in bilby, but it forces a total paradigm shift that most developers won't want to deal with. We could refactor then into separate bind- and fmap-alike methods, but in JavaScript that just makes them more brittle.

@pufuwozu:

It's a problem with promises if they do strange things with other monads. They shouldn't.

If we had a stronger type system, the Promise methods wouldn't even accept other monads as input. Functions that are total in statically typed systems are partial in JavaScript - and we don't even have a good way to restrict the domain manually. It sucks. 😦

@brian could you explain what strange things might happen in the context of
some other real monads such as option/maybe and a reactive pattern monad.
What does strange look like to a developer running across a problem.
On Apr 11, 2013 7:46 PM, "Brian McKenna" notifications@github.com wrote:

@erights https://github.com/erights but Promises/A+ already provides a
compatible then method. They are close to monadic. It's a problem with
promises if they do strange things with other monads. They shouldn't.


Reply to this email directly or view it on GitHubhttps://github.com//issues/94#issuecomment-16250312
.

@killdream By "stripped version of structural typing", if you mean, "duck typing", yes. And yes, it is sad. There are many sadder things that we live with because of historical constraints.

"every superset of { then :: Function } is considered a Promise =/". No. every superset of { then :: Function } is considered a thenable. That's an important difference.

"every superset of { then :: Function } is considered a Promise =/". No. every superset of { then :: Function } is considered a thenable. That's an important difference.

Is there somewhere that describes exactly what semantics a Thenable is expected to have? I've taken to calling monads in Javascript "Thenables", and I'd like to know if I'm reading into it too much.

commented

@erights well, it doesn't make much of a difference in this case, as it's more of an issue with them always being treated as something really close to a Promise in semantics, such that you can safely assimilate them.

It should be pretty obvious that interop with anything but promises is 100% out of scope for the promises spec and the promises community.

People still wanting examples:

// I'd implement this differently but I'd have to explain
function Optional(a) {
    this.hasValue = typeof a != 'undefined';
    this.value = a;
}
Optional.of = function(a) {
    return new Optional(a);
};
Optional.none = new Optional();
Optional.prototype.then = function(f) {
    if(!this.hasValue) return this;
    return f(this.value);
};

Let's write a single function to add my last name to both promises and optional values (assuming promises have constructor.of):

// Helper library
function map(x, f) {
    return x.then(function(a) {
        return x.constructor.of(f(a));
    });
}

function addLastName(m) {
    return map(m, function(name) {
        return name + ' McKenna';
    });
}

Promises in application.js:

addLastName(promisedNmae)
    .then(function(name) {
        // Brian McKenna
        console.log(name);
    });

Optional values in test.js:

addLastName(Optional.of("Brian"))
    .then(function(name) {
        // Brian McKenna
        console.log(name);
    });

@pufuwozu Thanks for all the time and attention you have given to providing this argument. My points were not to say /A+ promises were close enough, but rather that it seems that an A+ promise can be wrapped into a monadic value as you describe it. And in that sense, it may well be that the spec is "good enough" for both communities, the promises community that is focused on what seems a more intuitive (and powerful) interface focused on a particularized use case.

I think the point made by @Twisol is that the /A+ promises functionality of a two-parameter then cannot be identically reflected from a pure monadic promises implementation, but the pure monadic implementation can be built from the /A+ specification. This suggests to me that the status quo can be resolved simply with /A+, and a monadic wrapper surrounding it. Clearly, promises folk can't rely on libraries depending on monads without wrapping, but once wrapping they can have all the benefits, with the corresponding costs.

Where am I dropping the thread here?

I think the point made by @Twisol is that the /A+ promises functionality of a two-parameter then cannot be identically reflected from a pure monadic promises implementation

Just to clarify: The presence of a more powerful function doesn't mean that it's somehow not a "pure" monad. The promise wrapper you mention could easily expose a 2-ary then-like function; it's just outside the scope of what a monad requires. (See Haskell's Either and its either function, they're pretty directly analogous.)

@erights it's interesting that polotek/procstreams has been brought up a few times when people start talking about promises/monads, etc. I haven't been following all of the debate. I don't have that much of a vested interest. But since you've addressed me directly, I'll respond here for future reference.

Asking me to "fix my library" presumes that you've decided it's broken. It also presumes that I would agree with you that it's broken. My library works as designed for the most part. It's even got some tests on it (non-exhaustive as they may be). The point of contention here seems to be my use of the names "promise" and "then" in my design. Before I respond to that, let me say I actually really like the work done here with promises A+. Not because I subscribe to all of the ideas here. But because I love to see folks working together to improve their corner of the world. It's great work, and when/if I ever pick up a promise implementation, I'd take a second to check if it was A+ compliant. Because I think you all have put a lot of thought into things.

That said. I think it's a bit disingenuous to suggest to me and everyone else who ever uses the names "promise" and "then" that they should conform to A+. Even considering that I agree with what you're trying to accomplish here. There are several reasons for this. One is the presumption that I share your goal of interoperability. "until @polotek fixes his library, it can't co-exist with various promise libraries". Procstreams weren't designed to co-exist with promise libraries. No one has asked for it. I'm not sure how it has somehow become a requirement for your design, or how subsequently fixing that is my problem. Or maybe I'm misunderstanding your point. Are you saying that the promises A+ spec cannot and should not try to accomodate all Thenables in js? Because that I agree with.

I appreciate folks here for thinking of me. But procstreams probably won't change. Not because I'm a jerk. But because I like the way it is now. And more importantly, no one here has bothered establishing a connection with me that would make me inclined to accomodate you. If the plan is to effectively convert all Thenables in all libraries to support A+, this may come up a lot. Pulling people into random arguments and telling them to fix their code probably isn't the best tactic.

@polotek "Pulling people into random arguments and telling them to fix their code probably isn't the best tactic."

Hi Marco, you are correct. I apologize. I really like your clarification and agree with much of what you have to say.

commented

@tonymorris while @domenic's words weren't the best ones throughout this thread, I don't think we need any more fighting here.

@domenic Is there an accepted way to banish someone from further posts? I admin some email lists. After a post like @tonymorris's, I would banish that person, probably forever. But the mechanics of discussion on Github are new to me.

@erights I'm pretty sure there's no built in way to do so. It would probably be worth e-mailing GitHub support though, they may well help.