検索に移動やっと、我々は便利なものを手にします。変数は評価の結果を保存したり、あとから参照出来るようにしてくれます。Schemeの中では、変数は新しい値でリセットすることができますし、またプログラムの実行によって、その値は変化します。これは、Haskellにとっては複雑に見えます。いうのも、Haskellの実行モデルは、値を返す関数の上に構築されており、それらを変えることが出来ないからです。関わらず、Haskellでは同じ状態をシミュレートする方法がいくつかあり、全てモナドに関係します。このモナドは、任意の状態をモナドの中に隠しておいて、舞台裏で周囲に渡します。Integerという型を持ちます)。initialListを通じて、初期状態を記述します。残念なことに、このStateモナドは私たちのためには上手く動いてはくれません。いうのも、私たちが格納しておきたいデータの型というのは、かなり複雑だからです。LispVal)]でやり通すことができますが、しかし、私たちが関数の呼び出しを扱い始めると、これらのマッピングは、任意の深さの、入れ子になった環境のスタックになるでしょう。私たちがクロージャーを追加したとき、この環境はきっと任意の関数の値に保存され、そしてプログラム全体から返されるかもしれません。事実、容認できなくなるまで、値を保存し、runStateモナドは完全に潰されるでしょう。スレッドと呼ばれるものを使って、私たちの為に、この集合状態をHaskellに管理させましょう。これは変数を得たり、設定する為の機能を使って、他のプログラム言語と同じように、可変変数を扱うことができるようになります。Stateスレッドには二つの種類があります。残りのプログラムへ状態を回避させることなしに、単体で実行されるような、ステートフルな変数を作り出すSTモナドです。monadの中で、私たちにステートフルな変数を使わせてくれます。Data.IORefをimportすることから始めましょう。状態のための、型を決定します。LispVal)]これで、変化が多いLispValを文字列として対応した、IORefが持つリストとして、Envを定義できます。いうのも、Schemeが環境を変化させるためには、二つの方法があるからです。一つに、個々の変数の値を変化させ、明らかな変化を、この環境で共有するためのいくつかの機能を、set!で使えます(Schemeはスコープを入れ子にすることを許可しています。外のスコープにある変数も、全ての内部スコープから見えています)。全てのサブシークエントの式から見えるべきである、新しい変数を追加するためのdefineも使うことができます。IOモナドの内部の中でのみ使うことができます。空の環境を作るアクションのための手伝いを欲しがるでしょう。使うことができません。[]ここから、ちょっとだけ複雑になります。いうのも同時に二つのモナドを扱わないといけないからです。未束縛の変数のようなエラーを扱うため、Errorモナドが必要だったことを思い出して下さい。IO機能が必要な部分、および例外を投げることができる部分が入り組んでいるので、単純に全ての例外をキャッチして、IOモナドにノーマルな値を返すだけでは上手くいきません。Haskellは、モナド変換子として知られる、複数のモナドの機能を結合させる仕組みを提供しています。使うことになるでしょう。これを使って、IOモナドの上にエラー処理の階層を与えます。私たちの最初のステップでは、結合したモナドのために、型の別名を作ります。IOThrowsError同様、IOThrowsErrorは実際には型のコンストラクタです。関数の戻り値の型を渡すための最後の引数が省かれています。型と違い、もう1つの引数を取ります。この引数には、エラー処理のレイヤーで覆うモナドの型を渡します。これで、LispErrorをスローし、IOアクションを含むモナドを作れます。私たちは、IOThrowsErrorとThrowsErrorを混用します。違う型によるアクションは同じdoブロックの中に含めることが出来ません。それが、たとえ本質的には同じ機能を提供していたとしてもです。Haskellには、既に、下位の型(IO)の値を合成モナドへ入れるための仕組みがあります。残念なことに、未変換の上位型の値を、合成モナドの中へと入れる関数はありません。自分でそういうものを書く必要があります。valこれは、Either型のコンストラクタに応じて、エラーを投げ返すか、正常な値を返しています。型クラスのメソッドは、式の型に基づいて解決されます。IOThrowsErrorの定義が適用されます。ここで提供されている型のシグネチャは、完全に一般的ではありません。aと推論するでしょう。アクションを実行するヘルパー関数が必要です。これはIOアクションを返します。IOモナドでなければなりません。いうのも、IOを実行する関数は、外界に影響を与えるので、そのようなものを遅延評価される純粋関数の中には入れたくないからです。エラー計算を実行したり、エラーをキャッチすることは出来ます。extractValueこれは、エラーを捕捉し文字列表現に変換するのに前に定義したtrapError関数を使った後、runErrorTで全体の計算を走らせています。結果はextractValueに渡され、そしてIOモナドの中の値として返されます。今や私たちは本当に環境を扱う準備が出来ました。変数が既に環境に束縛されているかどうかを調べる関数から始めましょう。この関数はdefineを適切に処理するために必要になります。varまず最初に、readIORefを使って、IORefから実際の環境の値を取り出します。私たちが関心のある変数を探すため、この環境の値をlookupに渡します。lookupはMaybeの値を返すので、もしこの値がNothingならばFalseを返し、他の値ならTrueを返します(ここでconst関数を使います。いうのも、maybeが要求するのは、ただの値ではなく、結果を実行する関数だからです)。最後に値をIOモナドに持ち上げるためreturnを使います。ここではtrueかfalseかの値に興味があるので、lookupが返した実際のIORefを扱う必要はありません。次に、変数の現在の値を取り出す関数を定義します。env)これは、前出した関数のように、まず、IORefから実際の環境を取り出します。getVarはIothrowsErrorモナドを使います。いうのも、これは、エラー処理が必要だからです。liftIO関数を使う必要が出てきました。エラーを投げるために、liftIOを使う必要はありません。typeclassに定義されているからで、ErrorTはこのインスタンスだからです。関数に、値をセットする関数を作りましょう。valueまず、前と同じようにIORefから環境を読み込み、その結果にlookupを走らせています。ここでは、ただ読み込むだけでなく、値を変更したいのです。使い、writeIORefの引数を入れ替え、値を渡します。最後に、利便性のため、セットした値を返します。次は、変数が既に束縛されている場合は、変数をセットし、そうでない場合は、新しく変数を作成する、というdefineの動作を実現しましょう。前者の場合には、先に定義した値をセットする関数が使えます。value興味深いのは、後者の、変数が束縛されていない場合です。do記法を使って、ひとつのIOアクションを作っています。ペアを追加して、その新しいリストを書き戻しています。このdoブロック全体をliftIOを使って、IOThrowsErrorモナドへ持ち上げています。ひとつ便利な環境関数を作りましょう。この環境関数は、関数の実行に際して、変数の一群を一度に束縛することができます。次のセクションまで使うことはないでしょうが、今なら、これを上手く作成できます。ref)これは(do記法の代わりに)、モナディック・パイプラインとヘルパー関数を組み合わせているので、他の関数よりも複雑です。ヘルパー関数から始めるのが良いでしょう。作り、(name,value)のペアを返します。最終的にこれらのヘルパー関数をパイプラインの中で結びつけます。関数全体は、IORefが含む環境を読み出し、結果をextendEnvに投げ、拡張した環境を含む新しいIORefを返します。今や、全ての環境関数が用意できたので、評価器の中でこれらを使い始めましょう。Haskellはグローバル変数を持ちませんから、環境はパラメーターとして、評価器の中に入れなければなりません。特別なかたちを上手く追加できるでしょう。badForm対話型セッションの全体に通じて、ひとつの環境が使われるので、幾つかのIO関数を環境を取得するために変更する必要があります。env私たちはevalStringの中にあるrunIOThrowsが必要になります。いうのも、ThrowsErrorからIOThrowsErrorに変化させることができるからです。同様に、私たちはreadExprをIOThrowsErrorモナドに運ぶためのliftThrowsが必要になります。次に、私たちは、プログラムが始まる前に、null変数で環境を初期化しましょう。evalAndPrint単一の式を実行するため、新しい関数、runOneを追加しました。いうのは、今やただevalAndPrintをただ走らせるよりも複雑になっているからです。runReplの書き換えは、ちょっとだけとらえがたいものです。evalAndPrintの前に、オペレーターを構成する関数を如何に追加したか、思い出してください。開始とするEnvパラメーターが追加されているからです。アクションとして前の素朴なevalAndPrintを取る代わりに、モナディックパイプラインを下って来るもの、この場合ならnullEnvの結果を、最初にこの合成関数に適用するようにしています。env)で、これは、私たちが求めるものです。最後に、私たちはevalAndPrintを直接評価する代わりに、runOneを呼び出すように、main関数を変えてやる必要があります。argument"そして、コンパイルしたあとに、プログラムをテストしてみましょう。表示-継承ライセンスのもとで利用できます。追加の条件が適用される場合があります。詳細については利用規約を参照してください。統計Cookieに関する。