New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorporate monads and category theory #94

Closed
paulmillr opened this Issue Apr 10, 2013 · 229 comments

Comments

Projects
None yet
@paulmillr

paulmillr commented Apr 10, 2013

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

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Apr 10, 2013

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.

bergus commented Apr 10, 2013

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.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Apr 10, 2013

Member

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.

Member

domenic commented Apr 10, 2013

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.

@domenic domenic closed this Apr 10, 2013

@briancavalier

This comment has been minimized.

Show comment
Hide comment
@briancavalier

briancavalier Apr 10, 2013

Member

@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.

Member

briancavalier commented Apr 10, 2013

@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.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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.

puffnfresh commented Apr 10, 2013

@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.

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 10, 2013

Contributor

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.

Contributor

juandopazo commented Apr 10, 2013

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.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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.

puffnfresh commented Apr 10, 2013

@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.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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.

Member

ForbesLindesay commented Apr 10, 2013

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.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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.
Member

ForbesLindesay commented Apr 10, 2013

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.
@briancavalier

This comment has been minimized.

Show comment
Hide comment
@briancavalier

briancavalier Apr 10, 2013

Member

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.

Member

briancavalier commented Apr 10, 2013

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.

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 10, 2013

Contributor

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.

Contributor

juandopazo commented Apr 10, 2013

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.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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.

Member

ForbesLindesay commented Apr 10, 2013

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.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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.

puffnfresh commented Apr 10, 2013

@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.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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.

Member

ForbesLindesay commented Apr 10, 2013

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.

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 10, 2013

Contributor

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.

Contributor

juandopazo commented Apr 10, 2013

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.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

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.

puffnfresh commented Apr 10, 2013

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.

@Twisol

This comment has been minimized.

Show comment
Hide comment
@Twisol

Twisol Apr 10, 2013

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 commented Apr 10, 2013

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. 😀

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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 ;)

puffnfresh commented Apr 10, 2013

@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 ;)

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 10, 2013

Contributor

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

Contributor

juandopazo commented Apr 10, 2013

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

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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.

Member

ForbesLindesay commented Apr 10, 2013

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.

@kennknowles

This comment has been minimized.

Show comment
Hide comment
@kennknowles

kennknowles Apr 10, 2013

+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.

kennknowles commented Apr 10, 2013

+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.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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.

puffnfresh commented Apr 10, 2013

@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.

@Twisol

This comment has been minimized.

Show comment
Hide comment
@Twisol

Twisol Apr 10, 2013

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.

Twisol commented Apr 10, 2013

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.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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.

Member

ForbesLindesay commented Apr 10, 2013

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.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

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

puffnfresh commented Apr 10, 2013

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

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Apr 10, 2013

Member

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.

Member

domenic commented Apr 10, 2013

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.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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

Member

ForbesLindesay commented Apr 10, 2013

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

@Raynos

This comment has been minimized.

Show comment
Hide comment
@Raynos

Raynos Apr 10, 2013

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 dictatorship.

@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.

Raynos commented Apr 10, 2013

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 dictatorship.

@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.

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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.

puffnfresh commented Apr 10, 2013

@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.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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...

Member

ForbesLindesay commented Apr 10, 2013

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...

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 10, 2013

Member

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

Member

ForbesLindesay commented Apr 10, 2013

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

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 10, 2013

Contributor

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/

Contributor

juandopazo commented Apr 10, 2013

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/

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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!

puffnfresh commented Apr 10, 2013

@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!

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Apr 10, 2013

@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?

puffnfresh commented Apr 10, 2013

@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?

@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Apr 10, 2013

@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.

michaelficarra commented Apr 10, 2013

@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.

@Raynos

This comment has been minimized.

Show comment
Hide comment
@Raynos

Raynos Apr 10, 2013

@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.

Raynos commented Apr 10, 2013

@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.

@juandopazo

This comment has been minimized.

Show comment
Hide comment
@juandopazo

juandopazo Apr 10, 2013

Contributor

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.

Contributor

juandopazo commented Apr 10, 2013

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.

@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Apr 10, 2013

@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".

michaelficarra commented Apr 10, 2013

@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".

@Twisol

This comment has been minimized.

Show comment
Hide comment
@Twisol

Twisol Apr 10, 2013

@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.

Twisol commented Apr 10, 2013

@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.

@Raynos

This comment has been minimized.

Show comment
Hide comment
@Raynos

Raynos Apr 10, 2013

@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?

Raynos commented Apr 10, 2013

@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?

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

Also, as I understand it, the following would be a valid implementation of chain for all promise libraries (since the spec leaves behavior with incorrect types undefined):

Promise.prototype.chain = function (fn) {
  return this.then(fn);
}
Member

ForbesLindesay commented Apr 16, 2013

Also, as I understand it, the following would be a valid implementation of chain for all promise libraries (since the spec leaves behavior with incorrect types undefined):

Promise.prototype.chain = function (fn) {
  return this.then(fn);
}
@myrne

This comment has been minimized.

Show comment
Hide comment
@myrne

myrne Apr 16, 2013

The spec defines some very general "interfaces" which are not only defined by their method signatures, but also by some "relationships" which must hold. So then you can reason about all these different kind of objects in the same manner. Code may know how to fancy optimization tricks.

I have an example which may not be covered by the spec, but I think it's nice.
Let's define an object "obj" that has a method "transform". Now when you call obj.transform with value a, and you get in return value b, than you may be certain that any other time you call obj.transform with value a, you can be sure that the return value will be b again. Also, there will be no side effects. Or if there are, the method is "idempotent" (that's a term from REST, Roy Fielding). So if you have called the method once, you can call it any time extra and it doesn't matter.

Hence, return value doesn't have to be recomputed when input stays same. It can be cached. Memoization can be "externalized".

I can imagine many other fancy tricks are possible with strong specs. Perhaps not all performance related.

But this is cool. I'm in favor. I'm sold. Where can I get it? ;)

Oh yeah, I know REST and the concept of the uniform interface constraint.

myrne commented Apr 16, 2013

The spec defines some very general "interfaces" which are not only defined by their method signatures, but also by some "relationships" which must hold. So then you can reason about all these different kind of objects in the same manner. Code may know how to fancy optimization tricks.

I have an example which may not be covered by the spec, but I think it's nice.
Let's define an object "obj" that has a method "transform". Now when you call obj.transform with value a, and you get in return value b, than you may be certain that any other time you call obj.transform with value a, you can be sure that the return value will be b again. Also, there will be no side effects. Or if there are, the method is "idempotent" (that's a term from REST, Roy Fielding). So if you have called the method once, you can call it any time extra and it doesn't matter.

Hence, return value doesn't have to be recomputed when input stays same. It can be cached. Memoization can be "externalized".

I can imagine many other fancy tricks are possible with strong specs. Perhaps not all performance related.

But this is cool. I'm in favor. I'm sold. Where can I get it? ;)

Oh yeah, I know REST and the concept of the uniform interface constraint.

@myrne

This comment has been minimized.

Show comment
Hide comment
@myrne

myrne Apr 16, 2013

I think this is great, and if realized, may have great impact. It's hard to judge in advance though. What I do know, is that I'm charmed by the idea. In a way, the objects are the resources from REST. The "consuming" libraries are somewhat like web browsers. Connected via a limited amount of verbs, with strong restrictions imposed.

myrne commented Apr 16, 2013

I think this is great, and if realized, may have great impact. It's hard to judge in advance though. What I do know, is that I'm charmed by the idea. In a way, the objects are the resources from REST. The "consuming" libraries are somewhat like web browsers. Connected via a limited amount of verbs, with strong restrictions imposed.

@ForbesLindesay

This comment has been minimized.

Show comment
Hide comment
@ForbesLindesay

ForbesLindesay Apr 16, 2013

Member

This and some parallel discussions have all ultimately resulted in #101, which I hope will lead to something that makes everyone happy.

Member

ForbesLindesay commented Apr 16, 2013

This and some parallel discussions have all ultimately resulted in #101, which I hope will lead to something that makes everyone happy.

@medikoo

This comment has been minimized.

Show comment
Hide comment
@medikoo

medikoo Apr 16, 2013

@ForbesLindesay Your poc version, indeed enlightens a lot, great work! Thanks for that. We definitely need such bridge to JS land.

medikoo commented Apr 16, 2013

@ForbesLindesay Your poc version, indeed enlightens a lot, great work! Thanks for that. We definitely need such bridge to JS land.

@AriaFallah

This comment has been minimized.

Show comment
Hide comment
@AriaFallah

AriaFallah Feb 11, 2016

Well that was informative 😄 thanks @puffnfresh

AriaFallah commented Feb 11, 2016

Well that was informative 😄 thanks @puffnfresh

@donhatch

This comment has been minimized.

Show comment
Hide comment
@donhatch

donhatch Oct 22, 2016

In @domenic 's April 11 2013 comment, ey said "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".

Can someone provide some links to such discussions?
At this moment I have no idea why anyone thinks special-casing thenables is a good thing.

donhatch commented Oct 22, 2016

In @domenic 's April 11 2013 comment, ey said "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".

Can someone provide some links to such discussions?
At this moment I have no idea why anyone thinks special-casing thenables is a good thing.

@donhatch

This comment has been minimized.

Show comment
Hide comment
@donhatch

donhatch Feb 7, 2017

In answer to my Oct 21 question, I found these:

and some great discussion threads linked from comments on the latter (in particular, from the two Apr 25, 2013 comments).

donhatch commented Feb 7, 2017

In answer to my Oct 21 question, I found these:

and some great discussion threads linked from comments on the latter (in particular, from the two Apr 25, 2013 comments).

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Feb 16, 2018

Fucking Haskell-tards.

ghost commented Feb 16, 2018

Fucking Haskell-tards.

@tonymorris

This comment has been minimized.

Show comment
Hide comment
@tonymorris

tonymorris Feb 16, 2018

boohoo, I don't like it when someone understands the subject matter that I do not. When they helpfully explain the subject to me, it's especially bad, because tears come out! I am going to call them names instead, and grab a tissue. That will definitely resolve this matter.

tonymorris commented Feb 16, 2018

boohoo, I don't like it when someone understands the subject matter that I do not. When they helpfully explain the subject to me, it's especially bad, because tears come out! I am going to call them names instead, and grab a tissue. That will definitely resolve this matter.

@proc0

This comment has been minimized.

Show comment
Hide comment
@proc0

proc0 Feb 16, 2018

No need to redefine promises, which are tailored to ease the pain of asynchronous coding with imperative style. There are plenty of JS functional libraries that expose the Future Monad, which is essentially a Promise in FP land. For example - https://github.com/fluture-js/Fluture - or - https://github.com/mudge/pacta

proc0 commented Feb 16, 2018

No need to redefine promises, which are tailored to ease the pain of asynchronous coding with imperative style. There are plenty of JS functional libraries that expose the Future Monad, which is essentially a Promise in FP land. For example - https://github.com/fluture-js/Fluture - or - https://github.com/mudge/pacta

@puffnfresh

This comment has been minimized.

Show comment
Hide comment
@puffnfresh

puffnfresh Feb 16, 2018

@proc0 asynchronous imperative coding can be done better than Promises/A+ - those libraries (and my original, 5 year old post) show how.

puffnfresh commented Feb 16, 2018

@proc0 asynchronous imperative coding can be done better than Promises/A+ - those libraries (and my original, 5 year old post) show how.

@Risto-Stevcev

This comment has been minimized.

Show comment
Hide comment
@Risto-Stevcev

Risto-Stevcev Feb 17, 2018

Wow this thread is pretty depressing...
This proposal has nothing to do with haskell or the haskell community. If you have a bad perception of either of those it really shouldn't affect how you feel about this proposal in any way

Risto-Stevcev commented Feb 17, 2018

Wow this thread is pretty depressing...
This proposal has nothing to do with haskell or the haskell community. If you have a bad perception of either of those it really shouldn't affect how you feel about this proposal in any way

sderosiaux added a commit to sderosiaux/every-single-day-i-tldr that referenced this issue Apr 14, 2018

@dmitriz

This comment has been minimized.

Show comment
Hide comment
@dmitriz

dmitriz Apr 21, 2018

This example might be relevant to illustrate the danger of the current Promise implementation mixing .map and .chain behaviors into .then:
https://codepen.io/dmitriz/pen/rvNWxw?editors=0012

let val = {
	then: x => x
}
let val1 = {
	then: x => x(1)
}
let val2 = {
	then: {x:1}
}

// Nothing is getting out of this promise
// with  the `.resolve` not actually resolving
Promise.resolve(val).then(result => {
	console.log("I am silently ignored!")
	console.log("Result is:", result);
})
.catch(e => {
	console.log("I am also silently ignored!")
	console.log(e)
})

Promise.resolve(val1).then(result => {
	console.log("Result1 is:", result);
})

Promise.resolve(val2).then(result => {
	console.log("Result2 is:", result);
})

It can be argued that the case in the first example is rare, but if it might occur unexpectedly, the code becomes hard to debug and generally unsafe.

Is there any reason not to provide the same Promise class with the safer .map and .chain methods in addition to the (looking more convenient but) potentially dangerous .then?
Or is any library doing this already?

dmitriz commented Apr 21, 2018

This example might be relevant to illustrate the danger of the current Promise implementation mixing .map and .chain behaviors into .then:
https://codepen.io/dmitriz/pen/rvNWxw?editors=0012

let val = {
	then: x => x
}
let val1 = {
	then: x => x(1)
}
let val2 = {
	then: {x:1}
}

// Nothing is getting out of this promise
// with  the `.resolve` not actually resolving
Promise.resolve(val).then(result => {
	console.log("I am silently ignored!")
	console.log("Result is:", result);
})
.catch(e => {
	console.log("I am also silently ignored!")
	console.log(e)
})

Promise.resolve(val1).then(result => {
	console.log("Result1 is:", result);
})

Promise.resolve(val2).then(result => {
	console.log("Result2 is:", result);
})

It can be argued that the case in the first example is rare, but if it might occur unexpectedly, the code becomes hard to debug and generally unsafe.

Is there any reason not to provide the same Promise class with the safer .map and .chain methods in addition to the (looking more convenient but) potentially dangerous .then?
Or is any library doing this already?

@robotlolita

This comment has been minimized.

Show comment
Hide comment
@robotlolita

robotlolita Apr 21, 2018

@dmitriz we've discussed it extensively when ES6 was being discussed and the conclusion was: you can't have both (https://esdiscuss.org/topic/promise-cast-and-promise-resolve#content-40). They chose the non-monadic version of promises, and even though I still think that was a bad choice it's what we've got now.

You can just not use promises and write your own types, then build something like async/await on top of generators, and you'll get roughly the same characteristics Promises give you by default, but without the drawbacks (but also without the runtime optimisations).

robotlolita commented Apr 21, 2018

@dmitriz we've discussed it extensively when ES6 was being discussed and the conclusion was: you can't have both (https://esdiscuss.org/topic/promise-cast-and-promise-resolve#content-40). They chose the non-monadic version of promises, and even though I still think that was a bad choice it's what we've got now.

You can just not use promises and write your own types, then build something like async/await on top of generators, and you'll get roughly the same characteristics Promises give you by default, but without the drawbacks (but also without the runtime optimisations).

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Apr 21, 2018

You can just not use promises and write your own types, then build something like async/await on top of generators

…or use an existing library that does just that, like Creed :-)

bergus commented Apr 21, 2018

You can just not use promises and write your own types, then build something like async/await on top of generators

…or use an existing library that does just that, like Creed :-)

@dmitriz

This comment has been minimized.

Show comment
Hide comment
@dmitriz

dmitriz Apr 22, 2018

@robotlolita
Thank you very much for the link, an interesting read, when also frustrating for an outsider as everything refers to some context that is often to be found elsewhere or spread over multiple places. It is unfortunate that people now erroneously say Promises are monads, where people looking for explanation seem to have hard time finding it concisely in one place.

I must admit my PhD degree in Maths still does not help me to extract from that @BrendanEich comment nor the following comments debating these, a scientific rigorous proof that implementing .then on top of the more primitive .map and .flatMap (aka .chain) and exposing all of them on the Promise instance is fundamentally impossible.

Is there a such proof written anywhere that is complete, objective and not opinion based?

That is, a proof that is impossible to have a Promise class having exactly the same methods as currently implemented, plus the .map and .chain. (As a matter of personal opinion, I find .flatMap more descriptive than .chain that has already been used in JS to mean other things.)

You can just not use promises and write your own types, then build something like async/await on top of generators, and you'll get roughly the same characteristics Promises give you by default, but without the drawbacks (but also without the runtime optimisations).

Could you please explain the runtime optimisation? Wouldn't the native .chain be more performant (and .map even more), comparing to .then with all its plumbings?

dmitriz commented Apr 22, 2018

@robotlolita
Thank you very much for the link, an interesting read, when also frustrating for an outsider as everything refers to some context that is often to be found elsewhere or spread over multiple places. It is unfortunate that people now erroneously say Promises are monads, where people looking for explanation seem to have hard time finding it concisely in one place.

I must admit my PhD degree in Maths still does not help me to extract from that @BrendanEich comment nor the following comments debating these, a scientific rigorous proof that implementing .then on top of the more primitive .map and .flatMap (aka .chain) and exposing all of them on the Promise instance is fundamentally impossible.

Is there a such proof written anywhere that is complete, objective and not opinion based?

That is, a proof that is impossible to have a Promise class having exactly the same methods as currently implemented, plus the .map and .chain. (As a matter of personal opinion, I find .flatMap more descriptive than .chain that has already been used in JS to mean other things.)

You can just not use promises and write your own types, then build something like async/await on top of generators, and you'll get roughly the same characteristics Promises give you by default, but without the drawbacks (but also without the runtime optimisations).

Could you please explain the runtime optimisation? Wouldn't the native .chain be more performant (and .map even more), comparing to .then with all its plumbings?

@dmitriz

This comment has been minimized.

Show comment
Hide comment
@dmitriz

dmitriz Apr 22, 2018

@bergus

…or use an existing library that does just that, like Creed :-)

Thank you, this is what makes writing my comments worth is ;)

So if this library can implement all the methods simultaneously without breaking .then, doesn't it refute the arguments in https://esdiscuss.org/topic/promise-cast-and-promise-resolve ?

dmitriz commented Apr 22, 2018

@bergus

…or use an existing library that does just that, like Creed :-)

Thank you, this is what makes writing my comments worth is ;)

So if this library can implement all the methods simultaneously without breaking .then, doesn't it refute the arguments in https://esdiscuss.org/topic/promise-cast-and-promise-resolve ?

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Apr 22, 2018

@dmitriz

but also without the runtime optimisations

Actually that's wrong. Algebraic promises can be much better optimised than promises which need to do recursive flattening in every step.

Doesn't it refute the arguments in https://esdiscuss.org/topic/promise-cast-and-promise-resolve?

That's a very long and very old thread, even using names that had changed their meaning since. Which argument in particular do you refer to?

Creed does not break then, but it does break interoperability. You only get Promise/A+ assimilation interoperability by using the "broken" then method, which cannot be fixed and maintain compatibility (at least about recursive assimilation). You can add the more primitive operations like chain and of and map to an A+ compatible library, but they will only work with promises from that particular library.

bergus commented Apr 22, 2018

@dmitriz

but also without the runtime optimisations

Actually that's wrong. Algebraic promises can be much better optimised than promises which need to do recursive flattening in every step.

Doesn't it refute the arguments in https://esdiscuss.org/topic/promise-cast-and-promise-resolve?

That's a very long and very old thread, even using names that had changed their meaning since. Which argument in particular do you refer to?

Creed does not break then, but it does break interoperability. You only get Promise/A+ assimilation interoperability by using the "broken" then method, which cannot be fixed and maintain compatibility (at least about recursive assimilation). You can add the more primitive operations like chain and of and map to an A+ compatible library, but they will only work with promises from that particular library.

@robotlolita

This comment has been minimized.

Show comment
Hide comment
@robotlolita

robotlolita Apr 22, 2018

@dmitriz

a scientific rigorous proof that implementing .then on top of the more primitive .map and .flatMap (aka .chain) and exposing all of them on the Promise instance is fundamentally impossible.

It's possible to write that, and easy enough. The problem, really, is that it creates more room for confusing behaviour. People using .then expect promises to always be flattened, people using .chain expect promises to never be flattened. When you start using other people libraries, and those expectations combine in the code, you're most likely going to run into bugs that are hard to track because people had different expectations about how to go about writing the code.

The whole point of algebraic structures is to define a single way of composing structures such that you can always reason about their composition in a simpler way. If you start adding too many ways of composing values, that quickly falls apart, and you're left with none of the benefits.

robotlolita commented Apr 22, 2018

@dmitriz

a scientific rigorous proof that implementing .then on top of the more primitive .map and .flatMap (aka .chain) and exposing all of them on the Promise instance is fundamentally impossible.

It's possible to write that, and easy enough. The problem, really, is that it creates more room for confusing behaviour. People using .then expect promises to always be flattened, people using .chain expect promises to never be flattened. When you start using other people libraries, and those expectations combine in the code, you're most likely going to run into bugs that are hard to track because people had different expectations about how to go about writing the code.

The whole point of algebraic structures is to define a single way of composing structures such that you can always reason about their composition in a simpler way. If you start adding too many ways of composing values, that quickly falls apart, and you're left with none of the benefits.

@dmitriz

This comment has been minimized.

Show comment
Hide comment
@dmitriz

dmitriz Apr 23, 2018

@bergus

but also without the runtime optimisations

Actually that's wrong. Algebraic promises can be much better optimised than promises which need to do recursive flattening in every step.

That was my thought too. Perhaps @robotlolita meant the unwrapping optimisation if you re-implement it on top of .chain. But you can also use the native .then if you have to.

That's a very long and very old thread, even using names that had changed their meaning since. Which argument in particular do you refer to?

The argument that it is "fundamentally impossible to have both functionalities coexisting", e.g. here:
https://esdiscuss.org/topic/promise-cast-and-promise-resolve#content-40

Creed does not break then, but it does break interoperability. You only get Promise/A+ assimilation interoperability by using the "broken" then method, which cannot be fixed and maintain compatibility (at least about recursive assimilation). You can add the more primitive operations like chain and of and map to an A+ compatible library, but they will only work with promises from that particular library.

The monadic .chain (or .flatMap) should expect function returning the same monad. Do you mean, passing the native promise inside the .chain is not recognized by creed?

The .map and .of are simpler in that they merely wrap the value. So in what way should they work with other libraries?

@robotlolita

It's possible to write that, and easy enough. The problem, really, is that it creates more room for confusing behaviour. People using .then expect promises to always be flattened, people using .chain expect promises to never be flattened. When you start using other people libraries, and those expectations combine in the code, you're most likely going to run into bugs that are hard to track because people had different expectations about how to go about writing the code.

I don't quite understand how it is different from using any library X with two different methods a and b. Some folks will use a and get functionality of a, others will use b. Any compliant promise would have to provide both. The methods are called and work differently, yes. Confusion is of course always subjective and hard to debate for that reason, but is there any fundamental reason it would be impossible for the two methods to coexist?

The whole point of algebraic structures is to define a single way of composing structures such that you can always reason about their composition in a simpler way. If you start adding too many ways of composing values, that quickly falls apart, and you're left with none of the benefits.

An algebraic structure is just a function with several arguments, satisfying certain properties. Even integers have two separate algebraic structures -- + and * that perfectly coexist with no confusion.

Why there should be a single way to define it? I don't mean any personal preference here but a fundamentally possible way to satisfy different people's needs.

dmitriz commented Apr 23, 2018

@bergus

but also without the runtime optimisations

Actually that's wrong. Algebraic promises can be much better optimised than promises which need to do recursive flattening in every step.

That was my thought too. Perhaps @robotlolita meant the unwrapping optimisation if you re-implement it on top of .chain. But you can also use the native .then if you have to.

That's a very long and very old thread, even using names that had changed their meaning since. Which argument in particular do you refer to?

The argument that it is "fundamentally impossible to have both functionalities coexisting", e.g. here:
https://esdiscuss.org/topic/promise-cast-and-promise-resolve#content-40

Creed does not break then, but it does break interoperability. You only get Promise/A+ assimilation interoperability by using the "broken" then method, which cannot be fixed and maintain compatibility (at least about recursive assimilation). You can add the more primitive operations like chain and of and map to an A+ compatible library, but they will only work with promises from that particular library.

The monadic .chain (or .flatMap) should expect function returning the same monad. Do you mean, passing the native promise inside the .chain is not recognized by creed?

The .map and .of are simpler in that they merely wrap the value. So in what way should they work with other libraries?

@robotlolita

It's possible to write that, and easy enough. The problem, really, is that it creates more room for confusing behaviour. People using .then expect promises to always be flattened, people using .chain expect promises to never be flattened. When you start using other people libraries, and those expectations combine in the code, you're most likely going to run into bugs that are hard to track because people had different expectations about how to go about writing the code.

I don't quite understand how it is different from using any library X with two different methods a and b. Some folks will use a and get functionality of a, others will use b. Any compliant promise would have to provide both. The methods are called and work differently, yes. Confusion is of course always subjective and hard to debate for that reason, but is there any fundamental reason it would be impossible for the two methods to coexist?

The whole point of algebraic structures is to define a single way of composing structures such that you can always reason about their composition in a simpler way. If you start adding too many ways of composing values, that quickly falls apart, and you're left with none of the benefits.

An algebraic structure is just a function with several arguments, satisfying certain properties. Even integers have two separate algebraic structures -- + and * that perfectly coexist with no confusion.

Why there should be a single way to define it? I don't mean any personal preference here but a fundamentally possible way to satisfy different people's needs.

@dmitriz

This comment has been minimized.

Show comment
Hide comment
@dmitriz

dmitriz Apr 23, 2018

@bergus
I do not know if that addresses the interoperability concern,
but it seems creed.chain works as desired even with native promises:

> creed = require('creed')
> p = creed.resolve(1).chain(_ => Promise.resolve(2))
Promise { pending }
> p
Promise { fulfilled: 2 }

dmitriz commented Apr 23, 2018

@bergus
I do not know if that addresses the interoperability concern,
but it seems creed.chain works as desired even with native promises:

> creed = require('creed')
> p = creed.resolve(1).chain(_ => Promise.resolve(2))
Promise { pending }
> p
Promise { fulfilled: 2 }
@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Apr 23, 2018

@dmitriz, I mean something like

import creed from 'creed';
import other from 'some-other-algebraic-promise-lib';

const p = creed.resolve(other.of(other.of(1))); // similar, in creed.of(…).chain(_ => …)
p.map(console.log) // 1, or Other(1) ???

"Monadic" promises and "non-monadic" promises can coexist, yes, but they are incompatible with each other in terms of assimilation. One would need to spec a second algorithm (possibly even using then) that would not recursively unwrap.

bergus commented Apr 23, 2018

@dmitriz, I mean something like

import creed from 'creed';
import other from 'some-other-algebraic-promise-lib';

const p = creed.resolve(other.of(other.of(1))); // similar, in creed.of(…).chain(_ => …)
p.map(console.log) // 1, or Other(1) ???

"Monadic" promises and "non-monadic" promises can coexist, yes, but they are incompatible with each other in terms of assimilation. One would need to spec a second algorithm (possibly even using then) that would not recursively unwrap.

@dmitriz

This comment has been minimized.

Show comment
Hide comment
@dmitriz

dmitriz Apr 23, 2018

@bergus I really can't see any problems.

If your of here conforms to the Pointed Functor spec, then other.of(other.of(1)) should not unwrap like in [[1]], so nothing happens.
Whereas if resolve is unrwapping, similar to Promise.resolve, then in your example it should unwrap twice down to creed.resolve(1). (Apart from resolve being a suboptimal naming choice as Promise.resolve does not actually have to resolve if run over a promise.)

And the general principle should be simple too. You have the infinitely unwrapping then and resolve from any library, one-step-unwrapping flatMap and non-unwrapping map and of again, from any library. Any time there is a promise-conformant object from any library (or theneable), it should be unwrapped by the unwrapping methods. The infinitely unwrapping ones will continue until they can't unwrap anymore. That would actually simplify the current spec in half 😀 Am I missing something?

dmitriz commented Apr 23, 2018

@bergus I really can't see any problems.

If your of here conforms to the Pointed Functor spec, then other.of(other.of(1)) should not unwrap like in [[1]], so nothing happens.
Whereas if resolve is unrwapping, similar to Promise.resolve, then in your example it should unwrap twice down to creed.resolve(1). (Apart from resolve being a suboptimal naming choice as Promise.resolve does not actually have to resolve if run over a promise.)

And the general principle should be simple too. You have the infinitely unwrapping then and resolve from any library, one-step-unwrapping flatMap and non-unwrapping map and of again, from any library. Any time there is a promise-conformant object from any library (or theneable), it should be unwrapped by the unwrapping methods. The infinitely unwrapping ones will continue until they can't unwrap anymore. That would actually simplify the current spec in half 😀 Am I missing something?

@bergus

This comment has been minimized.

Show comment
Hide comment
@bergus

bergus Apr 23, 2018

@dmitriz TBH, I can see that solution as well, and have long been a proponent of "monadic promises" and wanted to avoid recursive unwrapping from the get-go (in A+). I'm probably not the one who can argue well against this. Now we just have to convince the ES committee and the engine implementors that this is a good idea…

bergus commented Apr 23, 2018

@dmitriz TBH, I can see that solution as well, and have long been a proponent of "monadic promises" and wanted to avoid recursive unwrapping from the get-go (in A+). I'm probably not the one who can argue well against this. Now we just have to convince the ES committee and the engine implementors that this is a good idea…

@dmitriz

This comment has been minimized.

Show comment
Hide comment
@dmitriz

dmitriz Apr 24, 2018

@bergus I have found another validation by @briancavalier that it is possible to implement of and chain without breaking then: #97 (comment)

That seems to be what creed is doing, where also wrapping Promises in Promises is possible via of. Surprisingly, I have read people's comments opposing the mere possibility to have Promises wrapped into Promises. Apparently some older promise libraries were against it, again for unclear reasons. It just looks frustratingly obscure for such an important decision affecting so many people.

dmitriz commented Apr 24, 2018

@bergus I have found another validation by @briancavalier that it is possible to implement of and chain without breaking then: #97 (comment)

That seems to be what creed is doing, where also wrapping Promises in Promises is possible via of. Surprisingly, I have read people's comments opposing the mere possibility to have Promises wrapped into Promises. Apparently some older promise libraries were against it, again for unclear reasons. It just looks frustratingly obscure for such an important decision affecting so many people.

@emilypi

This comment has been minimized.

Show comment
Hide comment
@emilypi

emilypi Aug 22, 2018

It's like I'm watching a chronological replaying of all the fundamental mistakes "pragmatic" developers make in their attempts to avoid learning something new, but encapsulated, in a nice, bite-sized thread.

@puffnfresh holy hell m8

emilypi commented Aug 22, 2018

It's like I'm watching a chronological replaying of all the fundamental mistakes "pragmatic" developers make in their attempts to avoid learning something new, but encapsulated, in a nice, bite-sized thread.

@puffnfresh holy hell m8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment