Are Promises Monads, Functor or Applicative
If we have ever programmed with JavaScript, we all know about Promises. The Promise object abstracts the future value of an asynchronous operation. It can be the eventual completion (or failure) of asynchronous result
Promises is very popular today. Many imperative programing languages adapt it. However, when we learn about functional programming, especially Haskell, sometimes we wonder that Promises are monads?
Haskell is near to mathematic. Each type class is a mathematic object. If we want to know a instance is a type of Functor, Monad or Applicative,we need to prove if the target is satisfy the laws.
Because each language has another mechanism to implement Promises, Promises can be different. In first section, I will talk about JavaScript.
Laws
Before go to the detail, let's a look at those laws. Note, p ≡ q simply means that you can replace p with q and vice-versa, and the behavior of your program will not change: p and q are equivalent.
Functor
fmap id ≡ id -- Identity law
fmap (g . f) ≡ fmap g . fmap f -- Associativity
Monad
return a >>= f ≡ f a -- Left identity
m >>= return ≡ m -- Right identity
(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g) -- Associativity
JavaScript
Are Promises Functor?
The analogous Promise function for fmap
is then
. The constructor is Promise.resolve
.
// id :: a -> a
const id = (a) => a
// fmap id ≡ id -- functor law
const lp = Promise.resolve(1).then(id)
const rp = id(Promise.resolve(1))
Promise.all([lp, rp]).then(([a, b]) => console.log(a === b))
// true
Promise seems satisfy Identity law. We can go next with Associativity law.
// fmap (g . f) ≡ fmap g . fmap f -- Associativity
const f = (a) => a * 2;
const g = (a) => a * 3;
Promise.all([
Promise.resolve(1).then(f).then(g),
Promise.resolve(1).then(a => g(f(a))),
]);
// true
Hooray. The result is same as the expectation. However:
// fmap (g . f) ≡ fmap g . fmap f -- Associativity
const f = (a) => Promise.resolve(a * 2);
const g = (pa) => pa.then((b) => b * 3);
Promise.resolve(1).then(f).then(g),
// TypeError: pa.then is not a function
If f
return a Promise, the law will be violated. The value that gets passed to f is implicitly unwrapped, causes TypeError
Answer: No
Are Promises Monads?
The analogous Promise function for bind
is then
. It is same as fmap
, right? In my option, because JavaScript is dynamic language, to make developers easier to learn, and not confuse about result type, the internal async promises inside then
is implicitly unwrapped. The analogous function for return
is Promise.resolve
.
// return a >>= f ≡ f a -- Left identity
const f = (a) => Promise.resolve(a * 2);
const lp = Promise.resolve(1).then(f);
const rp = f(1)
Promise.all([lp, rp]).then(([a, b]) => console.log(a === b));
It upholds the law. However, if a
is a Promise, the law is violated.
const f = (m) => m.then((a) => a * 2);
Promise.resolve(Promise.resolve(1)).then(f).then(console.log);
// TypeError: m.then is not a function
Because Promise implicitly unwrapped the Promise inside, the value in then
is no longer Promise. The TypeError
is raised. Second:
// m >>= return ≡ m -- Right identity
Promise.all([
Promise.resolve(1).then((a) => Promise.resolve(a)),
Promise.resolve(1)
]).then(([a, b]) => console.log(a === b));
// true
Right identity is satisfied, too. Note, we can't use free point style Promise.resolve(1).then(Promise.resolve)
. TypeError: PromiseResolve called on non-object
will be raised.
Finally, the Associativity law:
//(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g) -- Associativity
const f = x => Promise.resolve(x * 2);
const g = y => Promise.resolve(y * 3);
Promise.all([
Promise.resolve(1).then(f).then(g),
Promise.resolve(1).then((x) => f(x).then(g))
]).then(([a, b]) => console.log(a === b));
The law is satisfied. However, it is also violated if the input value is a Promise
//(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g) -- Associativity
const a = Promise.resolve(1);
const f = p => p.then(x => x * 2);
const g = y => Promise.resolve(y * 3);
Promise.all([
Promise.resolve(a).then(f).then(g),
Promise.resolve(a).then((x) => f(x).then(g))
]).then(([a, b]) => console.log(a === b));
// TypeError: p.then is not a function
Answer: No
So, how about Applicative?
Because Applicative class is extended from Functor class, so if Promises don't satisfy Functor class, Applicative don't satisfy too.
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Answer: No
Promises in Scala
In Scala, the author divide this asynchronous specification into 2 terms: Future
and Promise
, and we usually call it Future
. In brief:
A future is a placeholder object for a result that does not yet exist. A promise is a writable, single-assignment container, which completes a future. Promises can complete the future with a result to indicate success, or with an exception to indicate failure. Futures and Promises
Scala language is a combination of OOP and common function programming with static types. Of course it is much better than JavaScript too. With map
and flatMap
, it remove the confusion of JavaScript Promise
val fa = Future { 1 }
fa map { a => a * 2 }
// scala.concurrent.Future[Int]
fa map { a => Future { a * 2 } }
// scala.concurrent.Future[scala.concurrent.Future[Int]]
fa flatMap { a => Future { a * 2 } }
// scala.concurrent.Future[Int]
Answer: Yes All
References
- Futures and Promises - Kisalaya Prasad, Avanti Patil, and Heather Miller
- Monad Laws
- Functor
- Applicative Functors