「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
nullEnv :: IO Env
nullEnv = newIORef []
ここから、ことはちょっとだけ複雑になります。というのも同時に''二つ''のモナドを
多様なモナドが持つ機能を結合させるような、モナド変化として知られるようなメカニズムを、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のようなものは、実際にタイプのコンストラクタなのです。というのも、私たちは最後の引数や関数の返り値のタイプ
私たちは、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の定義を受けることが出来ます。 ついでに、ここで提供されている型のシグネチャは、
私たちは同様に、全般的に、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 -> String -> LispVal -> 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
興味深いのは、後者のケースで
もっと使える環境関数というのがあります。一度、全体の変数の枝葉を結び合わせることができ、関数が呼び出されたかのように実行される関数です。私たちは、きっとその機能を上手く作ることができます。ですから、次のセクションに移るまでに、それは使わないでおきましょう。
|