48

I've been learning about functional programming and have come across Monads, Functors and Applicatives.

From my understanding the following definitions apply:

a) ( A=>B ) => C[A] => C[B] | Functor

b) ( A=>C[B] ) => C[A] => C[B] | Monad

c) ( C[A=>B] ) => C[A] => C[B] | Applicative

(reference: https://thedet.wordpress.com/2012/04/28/functors-monads-applicatives-can-be-so-simple/)

Furthermore, I understand a Monad is a special case of a Functor. As in, it applies a function that returns a wrapped value to a wrapped value and returns a wrapped value.

When we use Promise.then(func), we are passing the Promise(i.e. C[A]) a function which normally has signature A => B and return another Promise (i.e. C[B]). So my thinking was that a Promise would only be a Functor and not a Monad as func returns B and not C[B].

However, googling I found out that a Promise is not only a Functor, but also a Monad. I wonder why, as func does not return a wrapped value C[B] but just B. What am I missing?

Jack Spar
  • 523
  • 1
  • 5
  • 6
  • 5
    Promises are not Functors or Monads. – zerkms Aug 16 '17 at 11:59
  • 5
    Promises could easily have been functors/monads, but unfortunately they are not. –  Aug 16 '17 at 12:03
  • @LukaszWiktor when referring other sites, it is often helpful to point that [cross-posting is frowned upon](https://meta.stackexchange.com/tags/cross-posting/info) – gnat Aug 16 '17 at 19:54
  • 3
    Firstly, the definitions you've stated (and as they're given in the blog post you've linked) are simply not complete - a functor is not *just* a function of type `(a -> b) -> C a -> C b`; it is also a set of laws which apply to said function (likewise for applicative functors and monads - furthermore these additionally require a function `a -> C a`). You say that "googling I found out that ..." but not where you've found this claim, or what the reasoning there was. – user2407038 Aug 20 '17 at 17:06
  • 5
    @zerkms Promises implement `bind` correctly, but unfortunately do not correctly implement `map` or `pure` due to their recursive flattening. They do however form a valid applicative, monadic functor over the restricted category of non-thenable types. – Asad Saeeduddin Apr 10 '18 at 17:32

4 Answers4

90

UDATE. See this new library providing functor and monad operators for plain callback-based functions that do not have the issues with theneables:

https://github.com/dmitriz/cpsfy


The JS Promise is neither a Functor nor an Applicative nor a Monad

It is not a functor, because the composition preservation law (sending compositions of functions to compositions of their images) is violated:

promise.then(x => g(f(x))) 

is NOT equivalent to

promise.then(f).then(g)

What this means in practical terms, it is never safe to refactor

promise
  .then(x => f(x))
  .then(y => g(y))

to

promise
  .then(x => g(f(x))

as it would have been, were Promise a functor.

Proof of the functor law violation. Here is a counter-example:

//Functor composition preservation law:
// promise.then(f).then(g)  vs  promise.then(x => g(f(x)))

// f takes function `x` 
// and saves it in object under `then` prop:
const f = x => ({then: x})

// g returns the `then` prop from object 
const g = obj => obj.then

// h = compose(g, f) is the identity
const h = x => g(f(x))

// fulfill promise with the identity function
const promise = Promise.resolve(a => a)

// this promise is fulfilled with the identity function
promise.then(h)
       .then(res => {
           console.log("then(h) returns: ", res)
       })
// => "then(h) returns: " a => a

// but this promise is never fulfilled
promise.then(f)
       .then(g)
       .then(res => {
           console.log("then(f).then(g) returns: ", res)
       })
// => ???

// because this one isn't:
promise.then(f)
       .then(res => {
           console.log("then(f) returns: ", res)
       })

Here is this example on Codepen: https://codepen.io/dmitriz/pen/QrMawp?editors=0011

Explanation

Since the composition h is the identity function, promise.then(h) simply adopts the state of promise, which is already fulfilled with the identity a => a.

On the other hand, f returns the so-called thenable:

1.2. “thenable” is an object or function that defines a then method.

To uphold the functor law, .then would have to simply wrap into promise the result f(x). Instead, the Promise Spec requires a different behavior when the function inside .then returns a "thenable". As per 2.3.3.3, the identity function id = a => a stored under then key is called as

id(resolvePromise, rejectPromise)

where resolvePromise and rejectPromise are two callback functions provided by the promise resolution procedure. But then, in order to be resolved or rejected, one of these callback functions must be called, which never happens! So the resulting promise remains in the pending state.

Conclusion

In this example, promise.then(x => g(f(x))) is fulfilled with the identity function a => a, whereas promise.then(f).then(g) remains in the pending state forever. Hence these two promises are not equivalent and therefore the functor law is violated.


Promise is neither a Monad nor an Applicative

Because even the natural transform law from the Pointed Functor Spec, that is part of being Applicative (the homomorphism law), is violated:

Promise.resolve(g(x)) is NOT equivalent to Promise.resolve(x).then(g)

Proof. Here is a counter-example:

// identity function saved under `then` prop
const v = ({then: a => a})

// `g` returns `then` prop from object 
const g = obj => obj.then

// `g(v)` is the identity function
Promise.resolve(g(v)).then(res => {
    console.log("resolve(g(v)) returns: ", res)
})
// => "resolve(g(v)) returns: " a => a

// `v` is unwrapped into promise that remains pending forever
// as it never calls any of the callbacks
Promise.resolve(v).then(g).then(res => {
    console.log("resolve(v).then(g) returns: ", res)
})
// => ???

This example on Codepen: https://codepen.io/dmitriz/pen/wjqyjY?editors=0011

Conclusion

In this example again one promise is fulfilled, whereas the other is pending, therefore the two are not equivalent in any sense, violating the law.


UPDATE.

What does exactly "being a Functor" mean?

There seems to be a confusion between Promise being a Functor/Applicative/Monad as it is, and ways to make it such by changing its methods or adding new ones. However, a Functor must have a map method (not necessarily under this name) already provided, and being a Functor clearly depends on the choice of this method. The actual name of the method does not play any role, as long as the laws are satisfied.

For the Promises, .then is the most natural choice, which fails the Functor law as explained below. None of the other Promise methods would make it a Functor either in any conceivable way, as far as I can see.

Changing or adding methods

It is a different matter whether other methods can be defined that conform to the laws. The only implementation in this direction that I am aware of is provided by the creed library.

But there is a considerable price to pay: not only entirely new map method needs to be defined, but also the promise objects themselves need to be changed: a creed promise can hold a "theneable" as value, while the native JS Promise can't. This change is substantial and necessary to avoid breaking the law in the examples as one explained below. In particular, I am not aware of any way to make the Promise into a Functor (or a Monad) without such fundamental changes.

Dmitri Zaitsev
  • 13,548
  • 11
  • 76
  • 110
  • 1
    Promises easily can form a monad. Why do you think that `then` would need to be a functor, that `then`+`resolve` would need to make a monad? – Bergi May 05 '18 at 15:21
  • 1
    @Bergi If you can make it a monad, please write a workable implementation. ;) – Dmitri Zaitsev May 06 '18 at 06:13
  • @Bergi `then` and `resolve` is what is provided by the API, and mentioned in the question. When people call promise a monad (mistakenly), they refer to what is provided. Are you suggesting to use other methods from the API? – Dmitri Zaitsev May 06 '18 at 06:33
  • @Bergi How is it different from the original Creed? – Dmitri Zaitsev May 06 '18 at 12:30
  • Not much, some internals are implemented more efficient. You just asked me to write a workable implementation, I've written several already :-) – Bergi May 06 '18 at 12:33
  • @Bergi Creed is a great library but it provides non-trivial extensions to both objects and methods. You can replace multiplication on natural numbers by addition on integers, but the latter does not make the former into a group ;) – Dmitri Zaitsev May 06 '18 at 13:32
  • 11
    Wrong answer. When we replace "under `then` prop" to under `foo` prop, the laws simply are satisfied. Monad law is not to hack reserved words in the programming language. –  Oct 20 '18 at 17:09
  • 7
    .@KenOKABE You are kind of right but `then` is not a reserved word in JS, is it? While I believe (with my very limited understanding of monads and friends) that OP is right in strict mathematical sense, promises still work in practical sense as monads. Tomas Petricek discusses this same difference in perspective in his excellent paper [What we talk about when we talk about monads](http://tomasp.net/academic/papers/monads/monads-programming.pdf). While something is not on a formal (mathematical) level a monad, it can still effectively be one from implementation perspective. – Roope Hakulinen Feb 14 '19 at 10:28
  • @user1028880 You are confusing the valid object key name `then` with reserved keywords. Not only you cannot exclude `then` from possible object key names, even attempting doing so would effectively eliminate all promises as such :) – Dmitri Zaitsev Aug 13 '19 at 09:03
  • outside the traps around using any function with `then` and `catch` references, is there anything else that would disqualify a promise from being a monad/functor? i get that something is either a functor/monad or not. Curious for sake of discussion how close the implementation of Promises is to being a functor/monad – Ralph Callaway Jun 16 '20 at 19:01
  • and for any language, is there a requirement that whatever the `map` method choice has to be a reserved word for this sort of counter proof not to be possible? – Ralph Callaway Jun 16 '20 at 19:02
  • @RoopeHakulinen I think you are making a great point here. From the strict algebraic perspective OP is right in pointing out that it is not a Monad and it cannot be (unless we stop talking about Promises as defined a we define them differently). Nevertheless it might be useful to refer to them as "monads" for the sake of some arguments (where the latter definition of monad is not an strict one but rather "something awfully-close-to-a-monad if it weren't for all the thenable mambo-jambo") – Claudio Feb 08 '21 at 21:13
  • 1
    Isn't this technically just writing an invalid implementation of .then ? By that standard, Haskell's monad typeclass wouldn't be a monad because you can write an invalid instance of the monad typeclass – saolof May 25 '21 at 08:39
  • I'd argued that you're wrong about the first item: we're used to handling functions in FP but arrays are still functors. I'd argue that promises are their own category and that their composition is `.then` and not function composition (which would be introspecting the promise anyway) the same way identity matrix is not the identity function. In that sense, we do have `p.then(f).then(g)` equivalent to `p.then(x => f(x).then(g))`. Using that perspective most of your reasoning falls apart as all are now true. Therefore, Promises are monadic via `then` as their composition tool. – Vivick Jan 26 '22 at 17:43
  • @Vivick The functor law needs to hold for all functions `f`, not just ones returning promises. If they don't, your 2nd expression `p.then(x => f(x).then(g))` may throw error. Are you suggesting changing the category by providing a new definition of the function composition? Which one and how would you make it work for __all__ functions? – Dmitri Zaitsev Jan 26 '22 at 18:38
11

Promise is (a lot like) a monad because then is overloaded.

When we use Promise.then(func), we are passing the Promise(i.e. C[A]) a function which normally has signature A => B and return another Promise (i.e. C[B]). So my thinking was that a Promise would only be a Functor and not a Monad as func returns B and not C[B].

This is true for then(Promise<A>, Func<A, B>) : Promise<B> (if you'll excuse my pseudocode for javascript types, I'll be describing functions as though this were the first argument)

The Promise API supplies another signature for then though, then(Promise<A>, Func<A, Promise<B>>) : Promise<B>. This version obviously fits the signature for monadic bind (>>=). Try it out yourself, it works.

However, fitting the signature for a monad doesn't mean that Promise is a monad. it also needs to satisfy the algebraic laws for monads.

The laws a monad must satisfy are the law of associativity

(m >>= f) >>= g ≡ m >>= ( \x -> (f x >>= g) )

and the laws of left and right identity

(return v) >>= f ≡ f v
m >>= return ≡ m

In JavaScript:

function assertEquivalent(px, py) {
    Promise.all([px, py]).then(([x, y]) => console.log(x === y));
}

var _return = x => Promise.resolve(x)
Promise.prototype.bind = Promise.prototype.then

var p = _return("foo")
var f = x => _return("bar")
var g = y => _return("baz")

assertEquivalent(
    p.bind(f).bind(g),
    p.bind(x => f(x).bind(g))
);

assertEquivalent(
    _return("foo").bind(f),
    f("foo")
);

assertEquivalent(
    p.bind(x => _return(x)),
    p
);

I think anyone familiar with promises can see that all of these should be true, but feel free to try it yourself.

Because Promise is a monad, we can derive ap and get an applicative out of it as well, giving us some very nice syntax with a little ill-advised hackery:

Promise.prototype.ap = function (px) {
    return this.then(f => px.then(x => f(x)));
}

Promise.prototype.fmap = function(f) {
    return this.then(x => f(x));
}

// to make things pretty and idiomatic
Function.prototype.doFmap = function(mx) {
    return mx.fmap(this);
}

var h = x => y => x + y

// (h <$> return "hello" <*> return "world") >>= printLn
h.doFmap(_return("hello, ")).ap(_return("world!")).bind(console.log)
Enlico
  • 23,259
  • 6
  • 48
  • 102
colinro
  • 417
  • 4
  • 10
  • 1
    Your `Function.prototype.fmap` is off. It really needs to be `Promise.prototype.fmap = Promise.prototype.then` and `_return("hello, ").fmap(h).ap(…)` - or if you prefer a static function instead of a method, `Promise.fmap(h, _return("hello, ")).ap(…)` – Bergi Feb 01 '18 at 00:57
  • @Bergi that would be more correct and generalizable, for the fmap implementation to belong to the 'functor'. Here I was mainly trying to draw a structural comparison to the equivalent haskell code, and to make it pretty I put the overspecialized fmap on the Function prototype. Unless you're saying the implementation is incorrect? – colinro Feb 01 '18 at 16:34
  • Given that functions are functors as well, it's just plain confusing (you would expect function composition when calling `fmap` on a function). – Bergi Feb 01 '18 at 16:37
  • 2
    `Promise` is not a monad, see my answer https://stackoverflow.com/a/50173415/1614973 – Dmitri Zaitsev May 06 '18 at 13:41
  • 1
    @DmitriZaitsev Wrong answer. When we replace "under `then` prop" to under `foo` prop, the laws simply are satisfied. Monad law is not to hack reserved words in the programming language. –  Oct 20 '18 at 17:10
  • @KenOKABE What precisely is wrong? If you refer to my answer, please explain below the answer. – Dmitri Zaitsev Nov 11 '18 at 19:56
5

Promises Are Not Monads Over Objects Containing a Then Property

Promises treat objects containing a then property which is a function as a special case. Because of this, they violate the law of left identity as below:

//Law of left identity is violated
// g(v) vs Promise.resolve(v).then(g)

// identity function saved under `then` prop
const v = ({then: x=>x({then: 1})})

// `g` returns the `then` prop from object wrapped in a promise
const g = (obj => Promise.resolve(obj.then))

g(v).then(res =>
          console.log("g(v) returns", res))
// "g(v) returns" x => x({ then: 1 })


Promise.resolve(v).then(g)
  .then(res =>
        console.log("Promise.resolve(v).then(g) returns", res))
// "Promise.resolve(v).then(g) returns" 1

example on codepen

This happens because resolve treats the function under the then property as a callback, passing the continuation of the then chain in as the argument rather than creating a promise containing it. In this way, it does not function like unit and causes a violation of the monad laws.

However, over values which do not contain a then property, it should function as a monad.

Community
  • 1
  • 1
Marty Gentillon
  • 127
  • 1
  • 3
  • A monad is based on chaining with function returning monads. In case of promises, this means returning promises, which are theneables. Thus, values with then property are at the core of functionality, so you can't exclude them. – Dmitri Zaitsev May 29 '19 at 03:07
-5

According to me, Promises are Functors, Applicative Functors and Monads since they obey the functor and monads laws.

Ok, lets study the functor case. For Promises to be an instance of Functor we must define an fmap function (a -> b) - f a -> f b for Promises and fmap shall pass the Functor laws. What are functor laws?

fmap id      = id
fmap (p . q) = (fmap p) . (fmap q)
  • id is identity function. We can simply implement it in JS like var id = x => x
  • The . in (p . q) is the composition operation just like in Math. It's essentially var dot = p => q => x => p(q(x)) in JS.

The problem in JS is that the objects, including the functions are reference types which means unlike in Haskell, every time you partially apply a function, you will get a different function doing the same thing. So just the equity checks in the following laws will fail but they will pass if you check the resulting values.

var id   = x => x,
    dot  = f => g => x => f(g(x)),
    fmap = f => p => p.then(v => f(v)),
    pr1 = Promise.resolve(1);
    
fmap(id)(pr1) === id(pr1); // false since objects are mutable
fmap(id)(pr1).then(v => console.log(v));
id(pr1).then(v=> console.log(v));

fmap(dot(x => x*2)(y => y+5))(pr1).then(v => console.log(v));
dot(fmap(x => x*2))(fmap(y => y+5))(pr1).then(v => console.log(v));

So yes Promises are Functors and if you check the Monad laws you can easily tell that they are also Monads.

Redu
  • 25,060
  • 6
  • 56
  • 76
  • 2
    `return a >>= f ≡ f a` this rule does not apply when `a` is a promise itself, since promises don't allow nested promises to be returned. – zerkms Aug 17 '17 at 09:36
  • 1
    @zerkms That holds perfectly... The type of `>>=` is `m a -> (a -> m a) -> m b`. So the type of `f` is `a -> m a` (which means take a simple value and return a monadic value like promise). On the other hand `return a` is `Promise.resolve(1)` and `(>>= f)` is just like JS `.then(f)` so if `var f = x => Promise.resolve(x*3)`; `Promise.resolve(1).then(f)` would return the same promise like `f(1)`. (Not the same object of course due to JS reference types) But yes Promises satisfy monadic laws. – Redu Aug 17 '17 at 09:53
  • 1
    `Promise`s are neither monadic nor functorial. There is no such thing as a monad, functor or any other concept from category theory in the promises spec. Just because they contain similar attributes, it doesn't mean that they should be treated as such. –  Aug 18 '17 at 13:51
  • @ftor Just because there is no such thing as a monad, functor or any other concept from category theory in the promises spec, it doesn't mean promises are not functors and monads. They obey the laws listed in my answer and they are of course perfect functors and monads. If you want your claim to be credible then please go on and prove otherwise. – Redu Aug 18 '17 at 14:45
  • 4
    `then` has the wrong type (somewhat like `Promise a ~> ? -> Promise b`), it is overloaded, it flattens recursively, it assimilates then-ables, it lifts functions automatically. And just providing some trivial examples doesn't make any proof. –  Aug 18 '17 at 15:58
  • @ftor Trivial or not.. I have proved that a Promise is a functor and a monad.. Unless you come up to prove otherwise for "me" they are monad... overloaded or not... They simply obey the law. – Redu Aug 18 '17 at 21:51
  • 1
    You seem to misunderstand what e.g. `fmap id = id` actually means - it doesn't mean that for one particular `x`, `fmap id x = id x`; it means this statement should be true *for every x*. You have not proved this latter claim. Furthermore - "if you check the Monad laws you can easily tell that they are also Monads" - this doesn't constitute a 'proof' in any way. The OP seems to be asking specifically about whether promises form a monad, but you've made no effort to prove, or even to informally demonstrate, this theorem. – user2407038 Aug 20 '17 at 17:18
  • @user2407038 As per `x`, `fmap` holds for every Promise object (by the exception of promises being mutable and just reference types in JS and i have already mentioned in my answer) and that's sufficient for me to qualify them as functors as much as JS allows. Yet as per them being monads; of course Promises are monadic and you most probably know that. Proving promises over monadic laws are straightforward and trivial yet i won't spend a single second of my life (other than my re-comment to @zerkms) any further. I would advise you to take your time and write your own answer. – Redu Aug 20 '17 at 18:29