関数型プログラミングが急速に広まり、"モナド"という関数型の構造がまた初心者を心底恐れさせています。数学の圏論の分野から拝借し、1990年代にプログラミング言語に導入されたモナドは、HaskellやScalaのような純粋関数型言語における根本的な構造です。
初心者の多くがモナドについて知っていることは、次のようなことです。
- モナドは入出力の実行に役立つ。
- モナドは入出力周辺のことに役立つ。
- モナドはモナドについての記事が詳細すぎるかほとんど詳細がないかであるため理解が難しい。
3番目の項目が私にこの記事を書かせる動機となりました。読者にモナドを紹介する100番目(もしかすると1000番目でさえあるかも)の記事です。運がよければ、この記事の終わりにはモナドはそんなに怖くないと感じるかもしれません。
システムの状態
コンピュータプログラムでは、"状態"という言葉はグローバルな変数や入力、出力、その他特定の関数にローカルでないものを表します。プログラムの状態について思い出してほしいことがいくつかあります。
- その値は、ある関数の実行から他の関数の実行まで持続する。
- それは、1つ以上の関数で利用可能である。
- それは、ミュータブルである。もしそうなら、その値は時間に依存するものである。
状態は管理しづらいものです。単一の関数が状態を所有するわけではないからです。次のシナリオを考えてみましょう。
- 1番の関数が状態から値を取得し、その命令実行で値を使用する。そうこうしているうちに、2番の関数がその値を変更する。結果として、1番の関数は最新でない値に基づいて判断する。
- さらに悪いことに、1番の関数はその古いデータを演算実行に使い、新しい不正確な値で古い値を置き換える。1番の関数のエラーが感染源となり、関数内からシステム全体に広がっていく。
いずれにせよ、システムの状態が時間の関数であり、そのため時間は考慮しなければならない余分な要素となります。"x
の値は何?"と問い合わせることは実際できません。代わりに、"時間t
におけるx
の値は何?"と問い合わせなければなりません。これではコードを判断するのが難しくなり、複雑な要素が増えます。結論としては...
状態あり: ダメ!
状態なし: グッド!
式とアクション
式とは値を持った文です。たとえば以下のコードを考えてみましょう。
x = 5
y = x + 7
x = y + 1
最初のx
は値が5であるという式です。最後のx
は値が13であるという式です。コードは他の式も含んでいます。たとえば真ん中の行は、x + 7
が12という値であるという式です。
ほとんどのコンピュータ言語では、キーボードから読み込むコマンドは式であり、その式は値を持っています。次の文を考えてみましょう。
x = nextInput()
この種の文はJavaやC++、その他多くのプログラミング言語で見つかるでしょう。さてユーザが数値5
を入力したところを想像してみましょう。そのあとnextInput()
は値が5
になる式です。文の実行でnextInput()
式の値(5
という値)を変数x
に割り当てます。もしx
がプログラムの状態の一部なら、この文は状態を変更します。すでに見てきたように、システムの状態を変更することは危険です。
nextInput()
の例では、nextInput()
の値は時間に依存します。式として一度nextInput()
を実行すると、その値は5
です。別の時間に式としてnextInput()
を実行すると、その値は17
です。型が弱い言語では、x
の値を5
から"Hello, world"
に変えられるでしょう。
時間依存を取り除くため、nextInput()
を使うのをやめ、doInput
という関数で置き換えます。式として、doInput()
の値は5
でも17
でも"Hello, world"
でもないです。代わりに、doInput()
の値はアクションです。実行時にキーボードから入力を取得するアクションです。
アクションは起こるかもしれないし起こらないかもしれないものです。キーボードからの読み込みはアクションです。"Hello, world"
をスクリーンに書き込むことはアクションです。"/Users/barry/myfile.txt"
にあるファイルを開くことはアクションです。http://www.infoq.com
への接続確立もアクションです。アクションはある種の演算です。一般的に、アクションの詳細はプログラムのソースコードで詳しく説明されません。代わりに、アクションは実行時の現象となります。
多くの言語で型について考えるとき、integerやfloat、string、booleanといったものを思い浮かべるでしょう。おそらく型としてアクションは思い浮かべないはずです。しかしモナドのI/Oをするとき、doInput()
のような式の値はアクションでです。やや違う方法でこれを言うと、doInput()
呼び出しからの戻り値は、アクション型の値です。
この考え方ではdoInput()
の値は時間依存ではありません。プログラムで式doInput()
が現れるどんなところでも、式はつねに同じ値です。その値はキーボードからの入力を取得するアクションです。
状態の有無の観点では、私たちは進展しています。
アクションの値では、多くのことはできない
少し問題があります。値がアクションである式で何か役立つことがしたいとします。どうすればできるでしょう?キーボードから数値の5を取得するアクションであれば、アクションに対して1を加算することはできません。
x = doInput()
print(x + 1)
この言語非依存のコードでは、変数x
はアクションを参照しています。アクションは1つの型であり、値1
は完全に別の型です。なので式x + 1
は意味をなしません。コンピュータを再起動するアクションに1を加算するに過ぎない、式restart + 1
はまったくナンセンスです!
もしユーザの入力に1
を加算したくなかったら?次のようなコードは意味をなすでしょうか?
x = doInput()
print(x)
いいえ、なしません。式x = doInput()
はx
にアクションを割り当てるので、式 print(x)
はアクションを表示しようとします。コンピュータスクリーンにアクションを表示しようとしたらどんな風に見えるでしょう?
意義を唱えるかもしれません。弱い型付き言語では、入力アクションとそれがキーボードから取得した値の違いを曖昧にできます。ちょうど2 == "2"
がJavaScriptで真となるように、ある弱い型付きの関数型言語で5 == the_action_obtaining_5
が真となるかもしれません。しかし、内部ではthe_action_obtaining_5
から5
を取得するためある種の処理を必要とします。それにまた、もし5
とthe_action_obtaining_5
の違いを曖昧にするなら、doInput()
ともともとのnextInput()
の間にはとても小さな違いしかありません。入力関数の呼び出しが時間依存の式であるという最初の地点に戻ってしまいました。これは望んだことではありません。
チェーン作成
事実が明らかになりました。doInput()
式の値は出力できません。しかしもしアクションを巧みにチェーンさせ、doInput()
アクションに出力する振る舞いを持つ他のアクションを続けさせられるとしたらどうでしょう?読者の方々、これがモナドです。
doInput()
アクションは5
のような値、ユーザがキーボードに入力した値とともに処理をするものです。doInput()
に取り組むとき、もっとも興味があることはユーザが入力した値5
であり、doInput()
アクション自身ではありません。
より詳しく見てみましょう。もしプログラムが倉庫の棚卸し表の記録をつけているものなら、入力の数字5は棚の上の箱の個数を表しているかもしれません。5という値はプログラムのドメインと密接に関係します。もし倉庫を管理してる人のところへ歩いて行って、"箱を5つ持っていますね"と言ったなら、その人はあなたが言った意味がわかります。一方、doInput()
式の値は棚卸し表を管理する人には意味がわからないアクションです。doInput()
アクションは棚卸し表を処理する方法の人工物だからです。この記事では、アクション自身とアクションに結びついた関連する値を区別します。アクションには密接な関係性が欠けていることを強調するため、私はアクション自身を人工物と呼びます。
まとめです。doInput()
を実行し、ユーザが数値5を入力したとき、
- 入力を受け取るアクションは私たちが人工物と呼ぶものである。
- 数値の5は私たちが関連する値と呼ぶものである。
この関連する/人工物という用語はすべてのモナドをそれぞれうまく扱えるわけではありませんが、モナドに関するすべてのことを説明する助けとなります。より理解の一助となるように、この記事の例の多くは数値の値を関連する値としていきます。
手抜きするなら、doInput()
アクションを5
のような関連する値を持つコンテナとして捉えます。コンテナはその中にあるものに対し快適な環境を作ります。コンテナのメタファは役立ちます。一度値がモナドと結びついたら、値はモナドと結びついたままにしておきたいからです。モナドコンテナの外に値を出したくありません。この関連とコンテナの比喩は本格的にモナドを扱う際には行き詰まる、ということをぜひ覚えておいてください。たとえそうでも、この比喩はモナドに関する直感を養う際に極めて役立ちます。
さあチャレンジです。doInput()
アクションに結びついている、関連するユーザの入力を使う方法を形式化しなければなりません(この記事では、"形式化"は"完全に厳密にすること"を意味しません。代わりに、"わかりやすい英語でのやや簡略化した記述を使って何かを正確にすること"を意味します。)。
いくつかの型はモナド、それ以外はモナドではない
一般的なプログラミング言語にはinteger型やfloat型、boolean型、string型、多くの種類の複合型があるでしょう。モナドまたはモナドでないものどちらでも型を記述できます。どんな型がモナド型でしょうか?
第一に、モナド型はそれに結びついた関連する値もしくは複数の関連する値を持ちます。関連する値を持つ型の例のいくつかです。
- I/Oアクション型:
キーボード入力アクションにとって、関連する値はユーザが入力するあらゆるものです。人工物はアクション自身です。 - リスト型:
このような値を持つリストを考えてみましょう。[3, 17, 24, 0, 1]です。このリストでは、関連する値は3、17、24、0、1です。人工物は値がリストを形成するために集められているという事実です。リストを思い浮かべたくないなら、配列でもベクターでもその他好きなプログラミング言語にあるコレクション構造を思い浮かべてください。 - Maybe型:
多くの言語でnull値は問題が多いものです。関数型プログラミングは回避手段を持っています。簡単なシナリオでは、Maybe値は数値(5のような)もしくはNothingを指すもののどちらかを持っています。Maybeはもし演算が値を決定できなかったときには値の代わりにNothingを持ちます。
JavaやSwiftといった言語はOptional型を持っています。OptionalはこのMaybe型に似ています。
Maybe値をもたらす演算にとって、関連する値は数値もしくは特別なNothingを指すものです。人工物は演算結果が単純に数値ではないという概念です。 - Writer型:
Writerはその仕事の一部として、進行中のログに書き込む情報を生み出す関数です。たとえば2乗を計算する関数で、2つ目の引数を受け取る手の込んだ関数を想像してみてください。
square(6) = (36, "false")
数値36
は6*6
です。"false"
という語は答え36
が10で割り切れないことを意味します。いくつかのアプリケーションが異なるWriter関数を実行すると、ログは次のように見えるでしょう。
"false true false"
この例では、関連する値は36
のような数値の結果です。人工物は文字列の値("true"
もしくは"false"
)と、そして文字列の値と数値の結果を束ねていることです。
それぞれの型に対してその型の関連する値の使い方を形式化するというのは、少しチャレンジです。とくに、
- I/Oアクション型に対して
アクションがあります。あるユーザ入力がアクションに結び付けられます。単に1をそのアクションに加算できず、コンピュータのスクリーンにそのアクションを出力できません。アクションは正確な型を持っていません。アクションと関連する値で何をどのようにするのか定義しなければなりません。
- リスト型に対して:
数値を持つリストがあります。例として3、17、24、、0、1がリストにあります。リスト自身の上で数値計算はできません。リストは正確な型を持っていません。リストにある各数値で何をどのようにするのか("+ 1"するといったことを)定義しなければなりません。
- Maybe型に対して:
2つのMaybe
値があると想像してみてください。maybe1
とmaybe2
という名前です。maybe1
の値はNothing
が結び付けられています。maybe2
の値は数値の5
が結び付けられています。maybe1
の値は成功しなかったとある計算から来ています。maybe2
の値は結果が5
であった計算から来ています。
maybe1
の値に1
を加算できるでしょうか?できません。Nothing
という値がmaybe1
と結びついています。 そのため1 + maybe1
は意味をなしません。
maybe2
の値に1
を加算できるでしょうか?モナドを学ぶという目的では、私の答えは依然としてノーです。値5
はmaybe2
と結びついていますが、maybe2
は5
と同一ではありません。maybe2
の値は数値ではないのです。これは人工的な構造です。たまたまそれに5
が結び付けられているのです。なので1 + maybe2
は意味をなしません。
ミッシングリンクはMaybe
値に結び付けられた関連する値で何をどのようにするのか定義しなければならない、ということです。
- Writer型に対して:
((6 * 6) + 4) / 5をする手助けとなる一般的な関数から始めてみましょう。
square(6) = 36
plus4(36) = 40
dividedBy5(40) = 8
ほしい答えを得るために、関数を複数チェーンできます。
dividedBy5(plus4(square(6))) = 8
しかし各関数がWriterだと、各関数の結果は10で割り切れることを示すもの、となり話は違ってきます。
蓄積したログには以下のことが含まれます。
square(6) = (36, "false") "false"
plus4(36) = (40, "true") "false true"
dividedBy5(40) = ( 8, "false") "false true false"
square
関数が返した組に対してplus4
関数を適用できません。
plus4(square(6))
は
plus4((36, "false"))
であり、これは意味をなしません。
代わりに、square
関数の実行から関連する値に対してplus4
を適用できます。同様に、plus4
関数の実行から関連する値に対してdividedBy5
関数を実行できるようにします。
1つの単純なルールで、一度にすべてを形式化できます。各関数呼び出しを直前の呼び出しの関連する値に対して適用する方法です。これは私たちにbind関数というものをもたらしてくれます。
モナドである型は、bind関数を持っていなければならない
多くのプログラミング言語で、型はそれ自身の関数を持っています。例えばinteger型は+、-、*、/という関数を持っています。string型はconcatenate関数を持っています。boolean型はor、and、notという関数を持っています。
モナドとしての資格を得るには、型はモナドの関連する値を使う関数を持たなければなりません。その関数は特定の形式をとらなければなりません。その形式がどのようなものかを見てみましょう。
関数の形式として最初の候補(正しくないアイデア)はモナドから関連する値を分離する関数(badIdea
と呼ぶことにします)です。たとえばbadIdea
関数がアクションからユーザ入力をつかむとしましょう。doInput()
を呼び出し、ユーザが数値5
を入力すると、badIdea(doInput())
は5
です。badIdea
関数を適用すると、badIdea()
呼び出しの結果の値を出力できます。badIdea()
呼び出しの結果の値に1
を加算することさえできます。
x = badIdea(doInput())
print(x)
y = x + 1
えーと、nextInput()
関数の時間依存問題を提起する前の、開始地点に戻ってきました。式badIdea(doInput())
はもともとのnextInput()
関数にある望ましくない機能をすべて持っています。一度badIdea(doInput())
を式として実行すると、その値は5
です。別のタイミングでbadIdea(doInput())
実行するとその値は17
となります。弱い型付き言語では、badIdea(doInput())
の値は5
から"Hello, world"
に変わるかもしれません。
badIdea
関数でdoInput()
アクションから関連する値をつかみその値で望む何かをします。この悪いアイデアを実装する代わりに、doInput()
から関連する値をつかみ、その値で何か役立つことをする別のアクションを生成しましょう。これは重要です。doInput()
アクションに別のアクションを続けます。このアイデアを形式化すると、アクションの型はモナド型となります。さあアイデアを形式化してみましょう。
2つのことから始めます。アクションと関数です。たとえば、
- アクション
doInput()
はキーボードから数値を取得します。
- 関数
doPrint
は数値を受け取り、スクリーンにその数を書き込むアクションを生み出します。
doPrint(5)
= スクリーンに5を書き込むアクション
doPrint(19)
= スクリーンに19を書き込むアクション
図1はこのシナリオの図解です。この図では各歯車は何かのアクションの立ち位置を描くものです。
doPrint
を数値からアクションへとして記述しています。関数型プログラミングをするとき、この種の関数は必然的ではありますが突如出現します。
私の表記にある単語に対し、少し立ち止まってみます。丸括弧のないopSystem
と丸括弧のあるopSystem()
という2つの式を考えてみましょう。opSystem
式の値は特定の関数です。現在実行しているオペレーションシステムに手を伸ばしそれを見つける関数です。しかしopSystem()
式の値は名前です。"Linux"
や"Windows 10"
のようなものです。要するに、
opSystem
は関数を表し、そしてopSystem()
はopSystem
関数の呼び出しから返された値を表します..
その点を考慮に入れて、丸括弧ありの“アクションdoInput()
”と丸括弧なしの“関数doPrint
” の違いに注目します。関数doInput
もdoPrint
も両方値を返します。両方の場合とも、値はアクションです。“アクションdoInput()
”と書く場合、doInput()
関数の呼び出しから返された値を参照しています。“関数doPrint
”と書く場合、doPrint
関数を参照しており、関数の戻り値の値ではありません。これは図1でdoInput()
とdoPrint
を解説した方法で違いを説明しています。doInput()
を説明するために歯車を描きました。これはみなさんにアクションを想起させるはずです。doPrint
を説明するために矢印を描きました。これは関数を想起させます。モナドについて考えるとき、これはそういった論点を思考の中心に置いてく手助けとなります。
doInput()
アクションとdoPrint
関数で、モナドを作るためにはもう1つのピースが必要です。doInput()
とdoPrint
を一緒にくっつける方法が必要です。より正確には、任意のアクションA
と任意の数値からアクションへの関数f
を受け取り、それらを組み合わせて新しいアクションを作る式です。この式にbind
という名前をつけます。図2を見ましょう。
先の段落ではbind
を式と呼びましたが、bind
は実際には別の関数です。
bind(A,f)
= 何かのアクション
bind
関数は高階関数です。bind
関数がその引数の1つとして関数を受け取るからです。関数の関数を考えることに慣れていないなら、bind関数で頭の中がぐるぐる混乱するかもしれません。
入出力のため、bind
関数は引数として任意のI/OアクションA
と数値からアクションへの関数f
を受け取る汎用的なルールでなければなりません。bind
関数は新規のI/Oアクションを返さなければなりません。たとえば、
doInput()
= キーボードから数値を取得するアクション
doPrint(x)
=x
の値をスクリーンに書き込むアクション
bind(doInput(),doPrint)
=
doInput()
の関連する値をスクリーンに書き込むアクション
図3を見てください。
ほんの少し例を変えてみましょう。
doPrintPlus1(x)
=x+1
の値をスクリーンに書き込むアクション
bind(doInput(),doPrintPlus1)
=
(doInput()
の関連する値)+1をスクリーンに書き込むアクション
図4を見てください。
一般的に、入出力のアクションA
と数値からアクションへの関数f
に対して、
bind(A,f) =
f
をA
の関連する値に適用するアクション
図5を見てください。
bind
の記述が混乱するものであっても、みなさんは独りではありません。高階関数は簡単に理解できないものです。
Haskellというプログラミング言語では、bind
関数はbind
演算子>>=
として言語に組み込まれるほど重要な役割を果たしています。実際、モナドを扱う多くの機能がHaskellに直接入っています。
もはや時間依存はない
図3で説明したdoInput()とdoPrintのシナリオに戻りましょう。
bind(doInput(),doPrint)
=
doInput()
の関連する値をスクリーンに書き込むアクション
bind(doInput(),doPrint)
式のどこにも時間依存の値はありません。コードでこの式が出てくるいかなる時でも、
doInput()
はつねに同じアクションです。キーボードから数値を取得するアクションです。doPrint
はつねに同じ数値からアクションへの関数です。- 式全体
bind(doInput(),doPrint)
はつねに同じアクションです。数値をスクリーンに書き込むアクションです。
ユーザがキーボードで入力した値は次々変わりますが、bind(doInput(),doPrint)
式のどこにも値を表す部分はありません。あらゆる副作用を取り除いてはいませんが、コードでシステムの状態に対し明示的に言及する部分は取り除きました。ひとたびユーザがキーボードで数値を入力すると、その数値は熱いポテトのようにあるアクションから別のアクションへ渡されます。ユーザが入力した値を表すコード上の変数はありません。
これは数値と入出力のアクションに限らない
これまで見てきた例で、
doInput()
の関連する値は数値であり、doPrint
の引数の型も数値である。doInput()
の型はアクションであり、doPrint
の戻り値の型はアクションである。
bind
関数はdoInput()
とdoPrint
を組み合わせ、新しいアクションを取得する方法を教えてくれます。
モナドのいくつかは数値や入出力のアクションとは何もすることがありません。では、doInput
とdoPrint
、bind
関数についての観察結果をより一般的な方法で述べてみましょう。
doPrint
の引数の型はdoInput()
の関連する値の型と同じです。doPrint
の戻り値の型はdoInput()
の型と同じです。
bind
関数はdoInput()
とdoPrint
を組み合わせて新しい値を取得する方法を教えてくれます。その新しい値はもともとのdoInput()
の値の型と同じ型です。
まだまだモナドの型
bind関数(と、この記事の終わりの直前に書いている他のある関数)を持つあらゆる型はモナドです。前の数段落で述べた入出力のためのbind関数で入出力のアクションの型はモナドになります。前述したリスト、Maybe、Writer型もモナド型です。リスト型でどのように動作するか見てみましょう。
2つのことから始めます。リストL
と関数f
です。もう一度言いますね、f
は特定の種類のものです。
- その引数として、
f
はその型がリストにある要素の型と同一である値を受け取ります。 - その結果、
f
はリストを作ります。
リストは数値を含む場合、f
は数値からリストへの関数です。任意のリストと数値からリストへの関数を受け取り、それらを組み合わせて新しいリストを作る公式(bind
の定義)はこうです。
bind(L,f)
=
Lの要素それぞれにfを適用し、結果であるリストのリストをフラット化したリスト
例を見てみましょう。
f(x)
= リスト[squareRoot(x),-squareRoot(x)]
とします。
要求として、f
は数値からリストへの関数です。
L = [4,25,81]
とします。
リストに対するbind
の定義から、
bind(L,f)
=
[[2,-2], [5,-5], [9,-9]]
をフラット化したもの
= [2,-2,5,-5,9,-9]
bind
関数はリストL
にあるあらゆる要素に関数f
を適用した結果の、可能性があるものすべてを与えてくれます。図6を見てください。
- 数値を含まないものの例です。
x
を本とします。そしてf(x)
= その本の著者のリストとします。
たとえば
f(C_Programming_Lang) = [Kernighan,Ritchie]
この例で、f
は本から著者への関数です。リストに対するbind
の定義から、
bind([C_Programming_Lang,
Design_Patterns], f) =
次のリストをフラット化したもの
[[Kernighan,Ritchie],[Gamma,Helm,Johnson Vlissides]] =
[Kernighan,Ritchie,Gamma,Helm,Johnson,Vlissides]
本のリストから著者のリストを何とか取得しました。図7を見てください。
図2から7まで類似していることに注目してください。
- 図の左側に、モナド値(たとえばアクションやリスト)と関数があります。関数は関連する値を引数に受け取りモナド値を作り出します。
- 図の中央に、
bind
関数の適用を指す矢印があります。 - 図の右側に、新しいモナド値があります。
Maybe型に対するbind
関数はどうでしょうか?m
をMaybeの値(数値もしくはNothingを含む)とし、f
を数値を受け取りMaybeの値を作り出す関数とします。bind(m,f)
の値はMaybeの値の内側にあるものに依存します。
bind(m,f)
= 以下のどちらか
f(mの関連する値)
もしm
がNothingを含んでいなければ
もしくは
Nothing もしm
がNothingを含んでいれば
例をいくつか並べてみましょう。
maybe1
がNothingを含むとします。
maybe2
が5を含むとします。
maybe3
が0を含むとします。
f(x)
= 100 / x
を含むMaybe値
すると
bind(maybe1,f) = Nothing
bind(maybe2,f) =
20を含むMaybe値
bind(maybe3,f) = Nothing
繰り返しになりますが、bind
関数の引数はモナド(この場合、Maybe値)と関数です。関数の引数はモナドの関連する値で、関数の戻り値は別のモナドです。
もちろんWriterモナドのためのbind
関数を作れます。
bind((aNumber,aString), f)
=
(
f(aNumber)
の数値部分, aString+
f(aNumber)
の文字列部分)
例をいくつか作ると、要点を理解する手助けとなります。square
やplus4
、dividedBy5
関数を思い出しましょう。その戻り値は10で割り切れるかを示すものを含む関数です。
bind((36, "false"), plus4) = (40, "false true")
図8を見てください。
bind((40, "false true"), dividedBy5) = (8, "false true false")
bind
を適用するとき、結果の文字列部分は先の計算からの文字列を含んでいます。文字列部分は全計算の実行ログです。
モナドにはもう1つ関数が必要
bind
関数は簡単には理解できないかもしれません。しかしモナドが持たなければならない他の関数はかなり単純です。私はそれをtoMonad
関数と呼んでいます。
toMonad
関数は引数として関連する値を受け取ります。toMonad
関数はモナドを返します。
大ざっぱに言うと、toMonad
は与えられた関連する値を含められるであろうもっとも単純なモナドを返します。(toMonad
とbind
の相互作用を含むより正確な定義がありますが、そこには立ち入りません。もし興味があるなら、https://en.wikibooks.org/wiki/Haskell/Understanding_monadsを見てください。)
- I/Oアクションモナドでは、
toMonad(aValue)
= その関連する値がaValue
であるが、その影響としてはまったく何もない、というアクションです。
- リストモナドでは、
toMonad(aValue)
=aValue
を唯一のエントリとして含むリストです。図9を見てください。
- Maybeモナドでは、
toMonad(number)
=number
を含むMaybe値です。
5を含むMaybe値が通常の数値5と厳密には同じでないことを思い出してください。
- Writerモナドでは、
toMonad
は空文字を含むモナドを返します。
toMonad(number) = (number, "")
これで全部!
モナドはbind
関数とtoMonad
関数を持つ型です。bind
関数はモナドの関連する値のベールを明示的に取ることなく機械的にモナドからモナドへ移ります。
関数型の思考をするとき、モナドは至る所に現れます。I/Oアクションモナドで、コード中にユーザの入力を表す式はなくなりました。そのためシステムの状態はプログラム中のどこにも明示的に表現されません。コードにある式は時間依存ではありません。"プログラムのこの時点においてx
が意味するものは何?"と問いかけることは一度もありません。代わりに、"このプログラムでずばりx
が意味するものは何?"と問うのです。
つまるところ...
状態あり: ダメ!
状態なし: グッド!
私たちが住む世界はステートフルです。この点についてできることは何もありません。モナドはシステムから状態を取り除くわけではありません。しかしモナドはプログラムコードから状態への言及を取り除きます。このことはとても有益なのです。
著者について
Barry Burd博士は今までたくさんの記事や書籍を書いてきました。有名な"Java For Dummies"や"Android Application Development All-in-One For Dummies"がそうです。2つともWiley Publishingから出版されています。またオライリーのIntroduction to Functional Programmingコースのインストラクタでもあります。Burd博士は1980年からニュージャージー州マディソンのドルー大学で数学とコンピュータ科学の教授です。アメリカやヨーロッパ、オーストラリア、アジアのカンファレンスで講演をしています。