ReaderTパターンで言語処理系のenvを作る
このノートはhsとpursが混合しているので注意mrsekut.icon
昔書いたhsに、pursで書き足しているので。
↓たぶんだけど間違ってるmrsekut.icon*3
Stateを使う
IORefとReaderを使う
これ
Readerモナドは読み取り専用のStateモナドで、各所からGlobal定数にアクセスしたいときなどに使う Readerを使わなくても、全ての関数の引数に持ち回せば実現できるが、こんな感じになる evalを作成するときにReaderを使うことでenvを持ち回らずにenvにアクセスができるので簡潔な実装になる
実際、Readerモナドは、昔はEnvironment Monadと呼ばれていたらしい IORef & Readerについて
状態がネストしたりと、程々に複雑で、変更が局所的な場合に用いると良い ref Readerモナドのみでは、環境から読み込むことしか出来ない
しかし、evalではAssignなど環境を書き換える必要も出てくる
そこでIORefと組み合わせる
ReaderとIORefを組み合わせることで、
Readr→envを持ち回さなくていい
IORef→envの書き換えができる
環境を表す型を定義する
code:purs(hs)
import Data.List (List(..))
import Control.Monad.Reader.Trans (ReaderT)
import Effect.Ref as Ref
type Env = Map String Expr
type EnvRef = Ref.Ref (List Env)
newtype ExprEnv a = ExprEnv (ReaderT EnvRef Effect a)
ホントは1行でも書けるが、扱いやすさのために3つにわけて定義しているmrsekut.icon
Envが1階層の環境
EnvRefが親の環境も含む環境
Exprは自分で定義した言語処理系のASTの型のこと
Map, List, Ref, ReaderTを使っていることいずれもポイントとなる
1つずつ見ていく
Map String ASTについて
これは1階層の環境の、変数名と式のMapになる
イメージ的には
これが
code:js
const a = 2
const b = true
こうなる
code:Env.hs
{ ("a", ExprInt 2), ("b", ExprBool True) }
冗長になるので、Map.fromList [..]を{ .. }と表記しているmrsekut.icon
List Envについて
local目線で、親の環境も含めた環境になる
Listの先頭要素が今の環境で、2つ目の要素が親の環境、3つ目がそのまた親の環境、..になる
こうすることで、親の環境と名前が被るときも区分して保存することができる
イメージ的には
このとき
code:js
// 視点①
const a = 2
const b = 10
const c = () => {
// 視点②
const a = 200
return a * b
}
こうなる
code:Env.hs
-- 視点①のList Env
code:Env.hs
-- 視点②のList Env
[ { ("a", ExprInt 200)}
, { ("a", ExprInt 2), ("b", ExprInt 10), ("c", ExprFn ..) }
]
どこの視点で見るかによってList Envの内容が変わるということもポイント
「アプリケーション全ての環境」を保持しているわけではない
その視点から参照しうる部分だけを保持しておけば十分
関数cの中で、aとbの演算を行っているが、その際に
今の環境にその変数があるならソレを使う(a)
今の環境にその変数がないなら親の環境を探す(b)
とやっていけば、仕様通りの実装ができることがわかる
Ref.Ref (..)について
List Env自体をRefで囲んでいる
順番を変えて、List (Ref.Ref Env)にするというケースもある
Ref.Ref Stringとすれば、そこに任意の文字列を可変参照できるのと同様に、
Ref.Ref (List Env)とすれば、環境をまるごと可変参照できるようになる
ReaderT EnvRef Effect aについて
EnvRefを参照できるように第2引数に定義している
環境から読み込むタイミングはいつか
envから読み込む
Var x: その変数束縛されている値を読み込む
App f x: その関数名に束縛されているargとbodyを読み込む
書き込む
Assign v x: 変数名に、値や関数を束縛する
Lambda arg body: argにbodyを束縛する
App f x: 仮引数に実引数を束縛する
参考