イントロダクション
JavaScript における Promise は失敗か成功のどちらかを表し、どちらの場合も何らかの値を持つことができる。さらにメソッドチェーンによりパイプラインのようなものを作ることができ、失敗すると short-circuit する。この点において Promise は Either monad の async 版のような感じがする。ここでは JavaScript の Promise と Scala の Either の対応関係を示す。
Note: 筆者は関数型プログラミングに詳しくない。
constructor
Promise の constructor である Promise.resolve
と Promise.reject
はそれぞれ Scala の Right
と Left
に対応している。
const r = Promise.resolve(1) // Resolve(1) と表記する
const l = Promise.reject(0) // Reject(0) と表記する
val r: Either[Int, Int] = Right(1)
val l: Either[Int, Int] = Left(0)
promise.then(f)
(1) = either.map(f)
Promise の then
メソッドに非 Promise を返す関数を渡したときの動作は Either の map
と同じである。
r.then(x => x + 10) // Resolve(11)
l.then(x => x + 10) // Reject(0)
r.map(_ + 10) // Right(11)
l.map(_ + 10) // Left(0)
promise.then(f)
(2) = either.flatMap(f)
Promise の then
メソッドに Promise (一重)を返す関数を渡したときの動作は Either の flatMap
と同じである。
r.then(x => Promise.resolve(3)) // Resolve(3)
r.then(x => Promise.reject(3)) // Reject(3)
l.then(x => Promise.resolve(3)) // Reject(0)
l.then(x => Promise.reject(3)) // Reject(0)
r.flatMap((x: Int) => Right[Int, Int](3)) // Right(3)
r.flatMap((x: Int) => Left[Int, Int](3)) // Left(3)
l.flatMap((x: Int) => Right[Int, Int](3)) // Left(0)
l.flatMap((x: Int) => Left[Int, Int](3)) // Left(0)
promise.catch(f)
(1) = either.left.flatMap(f andThen Right)
Promise の catch
メソッドに非 Promise を返す関数を渡したときの動作は Either の Left
に対してその関数を実行し、さらにその結果を Right
で包んで flatten
するのと同じである。
r.catch(x => x + 10) // Resolve(1)
l.catch(x => x + 10) // Resolve(10)
r.left.flatMap(((x: Int) => x + 10) andThen Right[Int, Int]) // Right(1)
l.left.flatMap(((x: Int) => x + 10) andThen Right[Int, Int]) // Right(10)
promise.catch(f)
(2) = either.left.flatMap(f)
Promise の catch
メソッドに Promise (一重)を返す関数を渡したときの動作は Either の Left
に対して flatMap
を実行するのと同じである。
r.catch(x => Promise.resolve(x + 10)) // Resolve(1)
r.catch(x => Promise.reject(x + 10)) // Resolve(1)
l.catch(x => Promise.resolve(x + 10)) // Resolve(10)
l.catch(x => Promise.reject(x + 10)) // Reject(10)
r.left.flatMap(x => Right[Int, Int](x + 10)) // Right(1)
r.left.flatMap(x => Left[Int, Int](x + 10)) // Right(1)
l.left.flatMap(x => Right[Int, Int](x + 10)) // Right(10)
l.left.flatMap(x => Left[Int, Int](x + 10)) // Left(10)
まとめ
以上の比較から、Promise の then
メソッドは map
と flatMap
のオーバーロードのようなものであるということがわかる(ちなみに関数の返り値が何重もの Promise になっていたとしても、一気に flatten するという性質がある)。
catch
メソッドについてはキレイに Scala コードで表現できなかったが、Left から Right に戻すという操作は関数型言語ではあまりしないのだろうか、と感じた。