私は、チュートリアルの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に代入する関数を次に示します。しかし、環境を変更するように関数を記述する方法がわかりません。BoolLispValbind
私の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ますか?