モナドがあって何が嬉しいのか
タイトルをちゃんと書くと「Haskellにモナドがあって何が嬉しいのか」
PureScriptなど、言語レベルでモナドをサポートする他の言語に置き換えても良い
注意
kindが*->*であるような型と、モナドを混合してはいけない
後者のほうが制限が強い
つまり、Functor型クラス、Applicative型クラスと、bindとreturnなどのInterfaceを持つ
モナドという概念全般の話と、個別具体的なモナドによる嬉しさとがある
後者はユースケース的なものであって、タイトルの直接的な解答にはなっていない
ここでは、特に前者に着目する
モナドの何がすごいのか、何が嬉しいのか
抽象化が上手すぎるおかげで、汎用性が高く、様々なものに応用できる
抽象的な概念を扱う圏論由来の概念なので、「抽象化の仕方」としての正しさが保証される
見た目全く異なるものを全て同じ2つの関数のみで定義できる
そのおかげで異なるものを同じ構文で表現できる
新しい構文を言語に導入する必要もなければ、覚える必要もない
Haskellが言語自体をモナドをベースとして実装していることも重要
Syntaxとの親和性が高く簡潔に書ける
だから「何でもかんでもモナドにする」ことがプラスになる
HaskellではListもモナドになってる
TypeScriptでも同じようなことをやるfp-tsとかあるが、冗長になる たまに、「○○言語でモナドやってみた」とかあるがそんなに嬉しくないことも多い
do記法はHaskellがモナドを扱うのが前提にあることで導入された糖衣構文 なくても書けるが、あった方が書きやすいし読みやすい
Functor, Monadといった構造が言語全体で共通していることで認知コストが下がる
抽象度の高い操作の組み合わせは一般的には読み取り難度が上がる
しかし、Haskellでは、それが言語全体で統一されているため、1つ学べば他に応用が効く
逆に、TypeScript内に唐突にBifunctorとか定義しても、それに慣れていない人からすれば、抽象度が高すぎるように見えて認知コストが上がってしまう
言語にない機能をモナドを使うことで言語拡張なしに利用できる ref table:例
Haskell TypeScript, Reactで言うと 補足
継続モナドCont async, await コールバック関数を見やすくしたい! これらを言語拡張なしに実装できる
つまり、「自分で」実装できる
しかも、シンプル、数行で定義できる
みんな同じものを使うのでライブラリとして提供されているだけ
実際にMaybeモナドや継続モナドの定義を見てみるとその簡単さがわかる
例えばTypeScriptでは、「非同期処理を手続き的に書きたい」という要望を満たすためには言語の内部をいじって、構文としてasync/awaitを実装する必要がある
一度入れたら取り外すのがきつい
下位互換をサポートするなら基本的には無理になる
使うモナドを変更するときに、コードの書き直しが不要になる
これはそこまで頻出する嬉しさではないが
Genericsの型の中身を変える感覚で、モナドの切り替えができる
これは以下2つによる効能
モナドのインタフェースが同じである
モナドが言語のベースにある
つまり、他言語のように「モナドっぽいもの」を多用するならこの効能は得られない
純粋関数の世界で副作用を扱える
これは純粋関数言語特有の話であって、「モナドの嬉しさ」の代表ではないと思うmrsekut.icon
Haskellが純粋であって、「モナドを使えばIOもイケるじゃん」になってる
語弊ありそう
IOがあって→モナド導入
たぶんこれ
Haskell 1.0のときにmonadはなかったはず
モナド→IO導入
同時
IOみたいなクソデカ概念に対しても対応できるモナドインターフェースは、やっぱすごいね、に繋がる
他言語使用者にとって、何がいいのか
わざわざ学ぶ意味はあるか
なんとも言えないmrsekut.icon
TSにモナドを持ち込んでもそこまで嬉しくはならない
上のテーブルに書いたように普通に目的のもの(e.g. OptionalChaining)が言語に搭載済みなので、わざわざモナドを定義して使うよりも、それを使ったほうが簡潔に書ける
強いて言うなら
圏論が楽しくなる
「X がモナドである」はなにが嬉しいのか
逆向きのパターンmrsekut.icon
この記事はたぶんモナドを知らないと理解できないのでは?と思った
ボトムアップ
monadの嬉しい点なのか?のやつ
昔書いた内容なので、正しさに疑問があるが、ここに書いてるのは「モナドのインターフェースの嬉しさ」だなmrsekut.icon
Monadに限らず、Applicativeなども混在している
一応残しているが参考になるかは微妙
mrsekut.iconにとっては益であるが、他者にとっては混乱を招くかも知れない
普通の関数a -> bは普通に関数合成.すれば組み合わすことが出来る
しかしモナド(というか型コンストラクタありの型?)は、単純に組み合わすことはできない
普通に考えて、これは具体例を提示すればモナドを知らなくても誰でもわかると思う
m aという値を扱いたいときに、どうやって関数合成をするの?を考えれば、モナドのような機能が欲しくなる
a -> m bとか、m (a -> b)みたいな関数があったときに
でも単純に考えればm a -> m bとかm a -> aって関数作ればええやんってなりそう
どう答える?
文脈が途中で消えるのは良くない、とか?
リストのときも同じ
こういう関数を定義して、その結果に対してlessThanを繰り返し使うことを考えると、普通の関数合成では無理だということがわかる
code:hs
あー、普通の型コンストラクタは違うか
parserを定義するときとかに型コンストラクタとか使ったけど、あれらは普通にパターンマッチで解決していた
何が違うのかと言うとやはり「文脈」ということば適切になってくるのか
文脈を持つから、「その場で解決」ではなく、「少し前の処理がどうだったか」の情報が必要になったりする
Maybeの話をしている
少し前にNothingになっていたら、今見ている処理もNothingになる、的な
関連
参考
作用を直接制御できると、同期処理とほとんど同じように非同期処理が書けたり、for文のような制御構造も要らなくなります。何もかもとてもシンプルになるのです