私は、チュートリアルのWrite Yourself A Scheme をフォローして拡張してきました。LispVal
モナドトランスフォーマーのいくつかのレイヤーにラップされた型があります。
import qualified Data.Map as M
data LispVal = ...
data LispError = ...
type Bindings = M.Map String (IORef LispVal)
data Env = Environment { parent :: Env, bindings :: IORef Bindings }
type IOThrowsError = ErrorT LispError IO
type EvalM = ReaderT Env IOThrowsError
using のアイデアReaderT
は、エバリュエーターを介して (変数バインディングを維持する) 環境を自動的に渡すことができるということask
です。これは、追加のパラメーターとして環境を明示的に渡すよりも望ましいようです。ContT
継続を実装するときは、モナド変換子で同様のトリックを行い、継続のための余分な引数を渡さないようにしたいと思います。
ただし、これを行うことで環境を変更する方法がわかりません。たとえば、新しい変数を定義したり、古い変数の値を設定したりします。
it
具体的な例として、if ステートメントを評価するたびに、変数をテスト句の結果にバインドしたいとします。私の最初の考えは、環境を直接変更することでした:
evalIf :: [LispVal] -> EvalM LispVal
evalIf [test, consequent, alternate] = do
result <- eval test
bind "it" result
case (truthVal result) of
True -> eval consequent
False -> eval alternate
aを anytruthVal
に代入する関数を次に示します。しかし、環境を変更するように関数を記述する方法がわかりません。Bool
LispVal
bind
私の2番目の考えは、使用することでしたlocal
:
evalIf :: [LispVal] -> EvalM LispVal
evalIf [test, consequent, alternate] = do
result <- eval test
local (bind "it" result) $ case (truthVal result) of
True -> eval consequent
False -> eval alternate
しかし、ここでbind
は type が必要でEnv -> Env
あり、環境内で s を値として使用してIORef
いるため、署名付きの関数しか記述できませんEnv -> IO Env
。
これは可能ですかStateT
、代わりに使用する必要がありReaderT
ますか?