『48時間でSchemeを書こう/変数と代入』のエラー処理の仕組みが分かったのでいよいよ変数と代入に関連する関数を見ることができる。この章で記述されているそれらの関数を概観すると次のようになる。
それでは、まず、isBound から眺めてみよう。 isBound :: Env -> String -> IO Bool isBound envRef var = readIORef envRef >>= return . maybe False (const True) . lookup var isBound の型をみると、Env型とString型の引数をとり、IO Bool 型の戻り値を返す。戻り値が IO a 型だから、この間数は IO モナド型の関数だ。 readIORef envRef で環境の変数テーブルを取り出し、それを lookup var に渡している。lookup は環境(変数テーブル)に変数名 var があるかどうかを検索し結果を Maybe モナド値として maybe 関数に渡す。maybe は lookup から渡された値が Nothing なら True を返し、Just x なら x に関数 (const True) を関数適用させた値を返す。この場合は const True だから、True が返る。最後に return でこの Bool 値を IO で包んで最終的な戻り値にしている。 関数 maybe の型は次のようになる。 Prelude> :t maybe maybe :: b -> (a -> b) -> Maybe a -> b 第3引き数が、Nothing のとき第1引き数を返し、Just x の時は、x に第2引き数の関数を関数適用する。実行例は次のようになる。 Prelude> maybe 1 (*2) Nothing 1 Prelude> maybe 1 (*2) (Just 2) 4 Maybe モナド型の関数を記述するときにパターンで定義したり、if 文を使わなくてもいいので便利だ。 つぎは、getVar 関数だ。プログラムは次のようになる。 getVar :: Env -> String -> IOThrowsError LispVal getVar envRef var = do env <- liftIO $ readIORef envRef maybe (throwError $ UnboundVar "Getting an unbound variable: " var) (liftIO . readIORef) (lookup var env) getVar 関数の型を見ると、Env型 (環境)とString型の引き数をとり、IOTrowsError LispVal 型の戻値を返す。したがって、環境のリストenvRef の中から変数名が var であるものを検索して、合致するものがあればその値を返し(liftIO . readIORef)、合致するものがなければエラーの例外を発生させる (throwError)。 liftIO という関数は Control.Monad.Trans に記述されており、型は次のようになる。 Prelude Control.Monad.Trans> :t liftIO liftIO :: (MonadIO m) => IO a -> m a これは IO a 型の値を、モナド型 m a に変換してくれる便利なものだ。上の例では、readIORef は IO (IORef a) の型の戻値を返すが、liftIO を適用することによって IOTrowsError 型モナドの値に変換している。そこで、listing8.hs をロードして試してみた。 Prelude> :set -XExistentialQuantification Prelude> :l listing8.hs Ok, modules loaded: Main. *Main> :t liftIO . readIORef liftIO . readIORef :: (MonadIO m) => IORef a -> m a liftIO でモナド変換できるモナド m は MonadIO クラスのインスタンスでなくてはならない。つまり、IOモナドとの複合モナドである必要があるようだ。 getVar の次は、setVar だが、処理は getVar とだいたい同じだ、変数の値を書きこむのに writeIORef が使われている。setVar はエラーがないときは LispVar を戻す。setVar は本文のコードをみればわかるので、型だけを次に示す。 setVar :: Env -> String -> LispVal -> IOThrowsError LispVal 次は defineVarだが、型は次のようになる。 defineVar :: Env -> String -> LispVal -> IOThrowsError LispVal setVar と型が同じだが、setVar が変数名がないときはエラーになるのに対し、defineVar では変数名が環境のリストにないときは新しく作ってリストに連結する。 最後に bindVars だが、型は次のようになる。 bindVars :: Env -> [(String, LispVal)] -> IO Env 型から分かる通り、変数名と値のリストを一気に環境リストに連結する関数だ。 仕上げは評価関数 eval と main 関数の改造だが省略する。 これまでの編集はhttp://jonathan.tang.name/files/scheme_in_48/code/listing8.hs に反映されているので、:load して実行してみた。 Prelude> :set -XExistentialQuantification Prelude> :l listing8.hs Ok, modules loaded: Main. *Main> :main Lisp>>> (define a 2) 2 Lisp>>> (+ (* a 3) 5) 11 Lisp>>> quit listing8.hs は358行だ。たったこれだけで、関数の定義以外のSchemeのかなりの部分を動作させてしまう。 今回の記事は複合モナド関連だったので、正直しんどかった。まだ、しっかりとは把握できていない。複合モナドについてもう少し要素的な実例があればうれしい。ただ、このチュートリアルのお陰で、IOモナドを扱いながら Error モナドを活用していくというような、入出力でよく出くわすであろう状況に対する対処の仕方のヒントをもらったような気がする。 今更ながら、熟練したHaskellerの技量に感銘をうけた。Haskell はとりつきは簡単なようで奥が深い。
by tnomura9
| 2011-12-30 09:44
| Haskell
|
Comments(0)
|
カテゴリ
新型コロナウイルス 主インデックス Haskell 記事リスト 圏論記事リスト 考えるということのリスト 考えるということ ラッセルのパラドックス Haskell Prelude Ocaml ボーカロイド 圏論 jQuery デモ HTML Python ツールボックス XAMPP Ruby ubuntu WordPress 脳の話 話のネタ リンク 幸福論 キリスト教 心の話 メモ 電子カルテ Dojo JavaScript C# NetWalker ed と sed HTML Raspberry Pi C 言語 命題論理 以前の記事
最新のトラックバック
最新のコメント
ファン
記事ランキング
ブログジャンル
画像一覧
|
ファン申請 |
||