BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル モナドを理解する - 迷える者への手引き

モナドを理解する - 迷える者への手引き

原文(投稿日:2017/11/24)へのリンク

関数型プログラミングが急速に広まり、"モナド"という関数型の構造がまた初心者を心底恐れさせています。数学の圏論の分野から拝借し、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を取得するためある種の処理を必要とします。それにまた、もし5the_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型、多くの種類の複合型があるでしょう。モナドまたはモナドでないものどちらでも型を記述できます。どんな型がモナド型でしょうか?

第一に、モナド型はそれに結びついた関連する値もしくは複数の関連する値を持ちます。関連する値を持つ型の例のいくつかです。

  1. I/Oアクション型:
    キーボード入力アクションにとって、関連する値はユーザが入力するあらゆるものです。人工物はアクション自身です。

  2. リスト型:
    このような値を持つリストを考えてみましょう。[3, 17, 24, 0, 1]です。このリストでは、関連する値は3、17、24、0、1です。人工物は値がリストを形成するために集められているという事実です。リストを思い浮かべたくないなら、配列でもベクターでもその他好きなプログラミング言語にあるコレクション構造を思い浮かべてください。

  3. Maybe型:
    多くの言語でnull値は問題が多いものです。関数型プログラミングは回避手段を持っています。簡単なシナリオでは、Maybe値は数値(5のような)もしくはNothingを指すもののどちらかを持っています。Maybeはもし演算が値を決定できなかったときには値の代わりにNothingを持ちます。

    JavaやSwiftといった言語はOptional型を持っています。OptionalはこのMaybe型に似ています。

    Maybe値をもたらす演算にとって、関連する値は数値もしくは特別なNothingを指すものです。人工物は演算結果が単純に数値ではないという概念です。

  4. Writer型:
    Writerはその仕事の一部として、進行中のログに書き込む情報を生み出す関数です。たとえば2乗を計算する関数で、2つ目の引数を受け取る手の込んだ関数を想像してみてください。

    square(6) = (36, "false")

    数値366*6です。"false"という語は答え36が10で割り切れないことを意味します。いくつかのアプリケーションが異なるWriter関数を実行すると、ログは次のように見えるでしょう。

    "false true false"

    この例では、関連する値は36のような数値の結果です。人工物は文字列の値("true"もしくは"false")と、そして文字列の値と数値の結果を束ねていることです。

それぞれの型に対してその型の関連する値の使い方を形式化するというのは、少しチャレンジです。とくに、

  1. I/Oアクション型に対して
    アクションがあります。あるユーザ入力がアクションに結び付けられます。単に1をそのアクションに加算できず、コンピュータのスクリーンにそのアクションを出力できません。アクションは正確な型を持っていません。アクションと関連する値で何をどのようにするのか定義しなければなりません。
     
  2. リスト型に対して:
    数値を持つリストがあります。例として3、17、24、、0、1がリストにあります。リスト自身の上で数値計算はできません。リストは正確な型を持っていません。リストにある各数値で何をどのようにするのか("+ 1"するといったことを)定義しなければなりません。
     
  3. Maybe型に対して:
    2つのMaybe値があると想像してみてください。maybe1maybe2という名前です。maybe1の値はNothingが結び付けられています。maybe2の値は数値の5が結び付けられています。maybe1の値は成功しなかったとある計算から来ています。maybe2の値は結果が5であった計算から来ています。

    maybe1の値に1を加算できるでしょうか?できません。Nothingという値がmaybe1と結びついています。 そのため1 + maybe1は意味をなしません。

    maybe2の値に1を加算できるでしょうか?モナドを学ぶという目的では、私の答えは依然としてノーです。値5maybe2と結びついていますが、maybe25と同一ではありません。maybe2の値は数値ではないのです。これは人工的な構造です。たまたまそれに5が結び付けられているのです。なので1 + maybe2は意味をなしません。

    ミッシングリンクはMaybe値に結び付けられた関連する値で何をどのようにするのか定義しなければならない、ということです。
     
  4. 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” の違いに注目します。関数doInputdoPrintも両方値を返します。両方の場合とも、値はアクションです。“アクション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) =  fAの関連する値に適用するアクション

図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を組み合わせ、新しいアクションを取得する方法を教えてくれます。

モナドのいくつかは数値や入出力のアクションとは何もすることがありません。では、doInputdoPrintbind関数についての観察結果をより一般的な方法で述べてみましょう。

  • 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)の文字列部分)

例をいくつか作ると、要点を理解する手助けとなります。squareplus4dividedBy5関数を思い出しましょう。その戻り値は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は与えられた関連する値を含められるであろうもっとも単純なモナドを返します。(toMonadbindの相互作用を含むより正確な定義がありますが、そこには立ち入りません。もし興味があるなら、https://en.wikibooks.org/wiki/Haskell/Understanding_monadsを見てください。)

  1. I/Oアクションモナドでは、toMonad(aValue) = その関連する値がaValueであるが、その影響としてはまったく何もない、というアクションです。
     
  2. リストモナドでは、toMonad(aValue) = aValueを唯一のエントリとして含むリストです。図9を見てください。

     
  3. Maybeモナドでは、toMonad(number) = numberを含むMaybe値です。

    5を含むMaybe値が通常の数値5と厳密には同じでないことを思い出してください。
     
  4. 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年からニュージャージー州マディソンのドルー大学で数学とコンピュータ科学の教授です。アメリカやヨーロッパ、オーストラリア、アジアのカンファレンスで講演をしています。

この記事に星をつける

おすすめ度
スタイル

BT