Skip to content
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

Native Promises cannot be Applicative Functors #1

Closed
Avaq opened this issue Jan 29, 2018 · 2 comments
Closed

Native Promises cannot be Applicative Functors #1

Avaq opened this issue Jan 29, 2018 · 2 comments

Comments

@Avaq
Copy link

Avaq commented Jan 29, 2018

I want to start by saying that I mean in no way to invalidate your work. I think you're creating a nice, low barrier stepping stone from Promises to Monads, and I appreciate it (and thank you for mentioning Fluture).

However, I do feel that it's important to acknowledge (and be more explicit about in your readme) the fact that we cannot build a completely correct Applicative Functor on top of current implementations of Promises/A+ (except Creed*). I've also tried to highlight this in the second part of "Specialised API" in my Broken Promises article.


The missing piece we need to implement a correct Applicative Functor is "higher order Promises" or Promises of Promises. The problem is that when a Promise (as it is currently implemented) sees another Promise, it assimilates it and assumes its state, holding as its value the value of the other Promise, instead of holding the other Promise directly. You've come across this: your chain implementation is an exact copy of your map implementation.

We can see this break the composition law:

F.map(x => f(g(x)), a) ≡ F.map(f, F.map(g, a))

const F = require('dashp')
const g = x => F.of(x + 1) // :: Number -> Promise Number
const f = F.chain(g)       // :: Promise Number -> Promise Number
const a = F.of(1)          // :: Promise Number


F.map(x => f(g(x)), a) // Promise { <resolved> 3 }
                       // ≡
F.map(f, F.map(g, a))  // Promise { <rejected> TypeError: Future#chain expects its second argument to be a promise }

Furthermore, Fantasy Land specifies that "no parts of a should be checked", where a is the value passed to of(). Promises unfortunately do check a, and respond to whether it is a Promise or not (for assimilation).

We can see this break the homomorphism law:

A.ap(A.of(f), A.of(x)) ≡ A.of(f(x))

const A = require('dashp')
const f = x => x + ' world' // :: Any -> String
const x = A.of('hello')     // :: Promise String

A.ap(A.of(f), A.of(x)) // Promise { <resolved> "hello world" }
                       // ≡
A.of(f(x))             // Promise { <resolved> "[object Promise] world" }

In conclusion, I do see the value of a utility library that helps you think of Promises in algebraic terms, but I would really like to see the readme educating people (maybe just via a link to this issue) about how it fails to be algebraic due to the limitation imposed by Promises.

* An interesting thing to note is that these flaws are not inherent to Promises/A+. Automatic assimilation is just an implementation choice. Unfortunately, native Promises went with that choice. It is actually possible to implement Promises/A+ while retaining algebraic properties. The aforementioned Creed is one such implementation. Perhaps DashP could be a stepping stone from native Promises to Creed, for those who prefer eager execution semantics.

critocrito added a commit that referenced this issue Feb 14, 2018
@critocrito
Copy link
Owner

Thanks so much for this comment. I was already wondering why chain and map are actually identical. Your explanations were very enlightening. Indeed, I don't attempt to build better Promises or such, I really just want to have a lightweight utility library that uses curried functions and that I can use with any existing Promise based code.

@Avaq
Copy link
Author

Avaq commented Feb 15, 2018

And thank you for hearing me out! :)

I really just want to have a lightweight utility library that uses curried functions and that I can use with any existing Promise based code.

Yeah, I definitely see it's use. I just didn't want any misconceptions to spread - your commit should be sufficient to avoid that. I'm very happy this has been enlightening! :)

@Avaq Avaq closed this as completed Mar 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants