「48時間でSchemeを書こう/変数と代入」の版間の差分

削除された内容 追加された内容
M編集の要約なし
編集の要約なし
13 行
type Env = IORef [(String, IORef LispVal)]
 
 これで、変化が多いLispValを文字列として対応した、IORefが持つリストとして、Envを定義できます。リストかそれ自身両方のための、そして個々の値のために、IO Refsが必要です。というのも、プログラムが環境を変化させるためには、二つの方法があるからです。まず一つに、個々の変数の値を変化させ、明らかな変化を、この環境で共有するためのいくつかの機能を、<span class="inline_lisp">set!</span>使えます(Schemeはスコープを入れ子にすることを許可しています。なので、外のスコープにある変数も、全ての内部スコープから見えています)。また、全てのサブシークエントの式から見えるべきである、新しい変数を追加するための<span class="inline_lisp">define</span>も使うことができます。
 IORefs は ただ、IO monadモナドの内部の中でのみ使うことができます。私たちは、空の環境を作るアクションのための手伝いを欲しがるでしょう。ただ、空のリストである [] は使うことができません。というのは、IORefsにアクセスするときは、必ず順番が決まっていなければならないからで、だから私たちの空っぽな環境の型は、ただの空のEnvの代わりに、IO Envになります。
 
nullEnv :: IO Env
nullEnv = newIORef []
 
 ここから、ことはちょっとだけ複雑になります。というのも同時に''二つ''のモナドをつかないといけないからです。覚えておいてください。私たちはまた束縛されていない変数のようなものを扱うためのErrorモナドが同様に必要になります。例外を投げることができる、IOの機能性が必要になるパーツは仲介され、そして私たちはただ全ての例外をキャッチしたり、またはIOモナドからただ値だけを返してもらうということができなくなります。
 
 多様なモナドが持つ機能を結合させるような、モナド変化として知られるようなメカニズムを、Haskellは供給しています。私たちは、きっとその一つを使うことでしょう。 - [http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#t%3aErrorT ErrorT] - これは、IOモナドのトップで、エラーハンドリングの機能性があるレイヤーを与えます。私たちの最初のステップは、私たちの結合したモナドのために、別の名前の型を作ってあげます。
 
type IOThrowsError = ErrorT LispError IO
 
 ThrowsErrorやIOThrowsErrorのようなものは、実際にタイプのコンストラクタなのです。というのも、私たちは最後の引数や関数の返り値のタイプを辞めたへと入れるからです。しかし、ErrorTは、まっさらな、古い、おのおのの引数から、一つ以上の引数を取ります。私たちは、自分たちのエラーを扱う機能のレイヤーにある、モナドの型を明示しなければならないのです。なので、LispErrorを投げるIOの行動を含めることができるモナドを作ります。
 
 私たちは、IOThrowsErrorとThrowsErrorのミックスがあります。しかし違う型によるアクションは同じdoブロックの中に含めることが出来ないのです。それが、たとえ本質的には同じ機能を提供していた、としても。Haskellは、低い型(IO)の値を、結合したモナドへ送るためのメカニズムが、既に提供されています。 [http://www.haskell.org/all_about_monads/html/transformers.html#lifting lifting]です。残念なことに、まだ変形していない、上位型の値を豪勢モナドの中へと運ぶ似たようなサポートはないので、私たちは自分たちでそういうものを書く必要があります。
34 行
liftThrows (Right val) = return val
 
 これは、それぞれの型と、エラー型か、元の値を返すかのどちらかを再び投げ返すという記述です。タイプクラスの中のメソッドは、例外の型を基礎に解決します。だから、[http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.html#v%3AthrowError throwError] や [http://www.haskell.org/onlinereport/standard-prelude.html#$tMonad return] ( [http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#t%3aMonadError MonadError] や [http://www.haskell.org/onlinereport/standard-prelude.html#$tMonad Monad], のメンバーです) は、IOThrowsErrorの定義を受けることが出来ます。 ついでに、ここで提供されている型のシグネチャは、完全に殆ど一般的ではありません。もし、これを私たちが使うなら、コンパイラーはきっと<span class="inline_code">liftThrows :: (MonadError m a) =&gt; Either e a -&gt; m a</span>と推測するでしょう。
 
 私たちは同様に、全般的に、IOのアクションを返してくれる、トップレベルのIOThrwosErrorのアクションを実行する手助けをしてくれる機能が必要です。IOモナドを回避することは出来ません。というのも、機能がIOに対して振る舞うのは、外の世界へ影響を持つからで、遅延評価された純粋関数では、望まれないことだろうからです。しかし、貴方はErrorを受け取ったり、実行したりすることが可能なのです。
60 行
 これは、前出した関数のように、IORefから実際の環境を取り出し始めます。しかし、getVarはIOthrowsErrorを使います。というのも、これも同様にいくつかのエラーを扱う必要があるからです。結局、私たちは、readIORefを取り出すための関数である、[http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Trans.html#v%3aliftIO liftIO]関数を使う必要が出てきました。同じように、私たちが値を返すときも、liftIOを使います。IOThrowsErrorアクションを作り出すためのreadIORefは、IORefの返り値を返します。しかし、私たちはエラーを投げてもらうために、liftIOを使う必要はなくなりました。というのも、throwErrorは[http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#t%3aMonadError MonadError typeclass]に定義されているからで、ErrorTはこのインスタンスです。
 
 関数に値をセットする関数を作りましょう。
setVar :: Env -&gt; String -&gt; LispVal -&gt; IOThrowsError LispVal
69 行
return value
 
 再び、まずこのIORefの外にある環境を読み込んで、この上にlookupを走らせます。しかし、このとき、私たちはただ読み込む代わりに、値を変えて欲しいと思ってます。[http://www.haskell.org/ghc/docs/6.4/html/libraries/base/Data.IORef.html#v%3awriteIORef writeIORef]アクションは、これらの意味を供給してくれますが、間違った命令(ref -> balue instead of value -> ref)のいくつかの引数を受けとります。だから、writeIORef周辺の引数を切り替えるためのビルトイン関数[http://www.haskell.org/onlinereport/standard-prelude.html#$vflip flip] 値をそこから、値を受けとります。最後に、便利に、ただセットしたい値を返します。
 
 セットする変数が、既に束縛されているものなのか、あるいは新しく作られたものなのか、そうではないのか、という[http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_sec_5.2 define]の特別な振る舞いを扱う関数も欲しいでしょう。だから、私たちは既に、値をセットする関数を定義したのであり、私たちは前のケースによって、この関数を作ることができます。
84 行
return value
 
 興味深いのは、後者のケースです。というのも値が束縛されていないからです。これで、(do-notation経路で)新しい値を持つための、新しいIORefを作り、環境における現在の値を読み込み、以前のリストに対して、変数と一致したペア(key,変数)を戻すための新しいリストを書くような、そんなIOアクションを作りました。そして、liftIOを使って、IOThrowsErrorモナドへと向かうことが出来ます。
 
 もっと使える環境関数というのがあります。一度、全体の変数の枝葉を結び合わせることができ、関数が呼び出されたかのように実行される関数です。私たちは、きっとその機能を上手く作ることができます。ですから、次のセクションに移るまでに、それは使わないでおきましょう。