tl; dr
- fp-ts-contrib の do notation に代わって、fp-ts 2.8.0 では
Option,Eitherなどの各種の Monad にbind,bindToというメソッドが生えた。- CHANGELOG
- fp-ts-contrib の依存なしに、do notation 的なコードがかけるようになった
- do notation と同様に、
bind,bindToを使うことで複数の Monadic な値の計算でコールバックによりネストが深くなる問題を解決できる。
do notation
PureScript など、一部のプログラミング言語には do notation と呼ばれる記法があります。PureScript の公式ドキュメントからコード例を引用します。
maybeSum :: Maybe Number -> Maybe Number -> Maybe Number
maybeSum a b = do
n <- a
m <- b
let result = n + m
pure result
maybeSum は Maybe Number な値を 2 つ取って、和を求めて Maybe Number で返す関数です。do キーワードを使うと、その下のブロックでは例えば n <- a と書くことで Maybe Number な a を Number 型の変数として n に束縛できます。これの何が嬉しいかは、do notation を使わなかった場合の例を考えるとわかります。
maybeSum :: Maybe Number -> Maybe Number -> Maybe Number
maybeSum a b =
bind a \n ->
bind b \m ->
let result = n + m
in pure result
bind a \n ->, bind b \m -> によりネストが二段深くなってしまいました。do notation を使った場合のコードとプログラムの内容が同じであるとすると、bindキーワードがどのようなはたらきをするかは直感的にわかるでしょう。今回の Post において重要なことは、「do notation を使うことで bind を使わずに済んでいる」、ということです(もしくはその逆)。PureScript の公式ドキュメントでは、bind x \a -> ... が a <- x の糖衣構文であることが明記されています。
a <- xwhich desugars to bindx \a -> ...
fp-ts における do notation
話を PureScript から TypeScript というプログラミング言語に移します。TypeScript には標準で Monad や do notation のサポートがありません。しかし、 gcanti/fp-ts などのライブラリを利用することで Monadic なコードが書けるようになります。
ただ、do notation については gcanti/fp-ts-contribという別パッケージに切り出されていました。fp-ts-contrib が提供していた Do 、次のようにして使われていました。
import { Do } from "fp-ts-contrib/lib/Do";
import { option, some } from "fp-ts/lib/Option";
const result = Do(option)
.bind("a", some(1))
.bind("b", some(2))
.return(({ a, b }) => a + b);
ここからようやく本題に入っていくのですが、上記のコードが fp-ts 2.8.0 からは以下のように書けるようになりますよというのが今回共有したかった話です。
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
const result = pipe(
O.bindTo("a")(O.some(1)),
O.bind("b", () => O.some(2)),
O.map(({ a, b }) => a + b)
);
}
Option などの各モナドごとに bind, bindTo が提供され、 fp-ts-contrib に依存せず do notation 的なコードが書けるようになりました。
O.bind("b", () => O.some(2)) を O.bindTo("b", O.some(2)) と書きたくなりますが、O.bind と O.bindTo は引数の取り方が違う上、bindTo では pipe で流れてくる Option<A> の A の情報を引き継ぐことができません。({ a: O.some(1) }を引き継ぐことができない)
上記のコードのようなケースでは、 bindTo もしくは bind を pipe の先頭で使い、以降は bind で繋いでいくという方針で書いていくと良さそうです。
おわりに
この記事はアルバイト先の株式会社 HERP のまざっちさん(@mazamachi)に勧められて書きました。また、まざっちさんには執筆の上でのアドバイスをいただきました。ありがとうございます 🙌
HERP では、TypeScript の高度な型システムや fp-ts などの関数型プログラミングのライブラリを駆使して、混沌とした Web フロントエンドの世界をともに生き抜いていくソフトウェアエンジニアを募集しております。オフィスは東京・京都・つくばにあってフルリモートも多分可能です。いい会社です!
Top comments (0)