elab API

elab パッケージ(Luna-Flow/QED/elab)は、名前とカーネルの間の解決の境界である。項の各名前をローカル文脈とカーネル状態に対して一度だけ解決し、定数がカーネル上の同一性を持つ解決済み項 RTerm にする。解決済み項を状態に対して型検査し、カーネル項へローワリングする。また、型検査付きでカーネル項を構築する薄いビルダーも提供する。kernel のみに依存し、定理は生成しない。

凍結された同一性の背後にある設計は elab 設計ノートにあり、elab チュートリアルでは解決とスコープの変更を順に解説する。

文脈

ElabCtx

ElabCtx はローカル文脈であり、変数名とその型の順序付きリストで、最も内側が末尾に来る。

pub struct ElabCtx {
  locals : Array[(String, @kernel.HolType)]
}

empty_elab_ctx、elab_ctx_from_locals、elab_ctx_extend

これらの関数は文脈を構築する。elab_ctx_extend はローカルを 1 つ追加した新しい文脈を返し、引数は変更されない。

pub fn empty_elab_ctx() -> ElabCtx
pub fn elab_ctx_from_locals(Array[(String, @kernel.HolType)]) -> ElabCtx
pub fn elab_ctx_extend(ElabCtx, String, @kernel.HolType) -> ElabCtx

同じ名前を持つ後のローカルは、先のローカルをシャドーイングする。

解決済み項

ResolvedConst

ResolvedConst は、解決時に凍結された定数の出現である。

pub struct ResolvedConst {
  name : String
  const_id : Int
  inst_ty : @kernel.HolType
  schema_ty : @kernel.HolType
}

const_id は名前が解決された先のカーネル上の同一性、schema_ty はその時点で宣言されていたスキーマ、inst_ty はこの出現の型であり schema_ty のインスタンスである。組み込みの等号は const_id == -1 で、スキーマは α→α→bool\alpha \to \alpha \to \mathit{bool} である。

RTerm

RTerm は解決済み項であり、すべての定数が ResolvedConst である名前付きの項である。

pub enum RTerm {
  RVar(String, @kernel.HolType)
  RConst(ResolvedConst)
  RComb(RTerm, RTerm)
  RAbs(String, @kernel.HolType, RTerm)
}

rterm_var、rterm_const、rterm_comb、rterm_abs

これらの関数は、検査なしで解決済み項を構築する。

pub fn rterm_var(String, @kernel.HolType) -> RTerm
pub fn rterm_const(ResolvedConst) -> RTerm
pub fn rterm_comb(RTerm, RTerm) -> RTerm
pub fn rterm_abs(String, @kernel.HolType, RTerm) -> RTerm

rconst_name、rconst_id、rconst_inst_ty、rconst_schema_ty

これらの関数は ResolvedConst のフィールドを読み出す。

pub fn rconst_name(ResolvedConst) -> String
pub fn rconst_id(ResolvedConst) -> Int
pub fn rconst_inst_ty(ResolvedConst) -> @kernel.HolType
pub fn rconst_schema_ty(ResolvedConst) -> @kernel.HolType

rterm_eq

rterm_eq は解決済み項の構造的等価性であり、束縛名と定数の同一性を含む。α 同値ではない。

pub fn rterm_eq(RTerm, RTerm) -> Bool

rterm_collect_free_vars と rterm_collect_consts

rterm_collect_free_vars は解決済み項の自由変数を、出現順に重複を含めて列挙する。rterm_collect_consts はすべての定数の出現を列挙する。

pub fn rterm_collect_free_vars(RTerm) -> Array[(String, @kernel.HolType)]
pub fn rterm_collect_consts(RTerm) -> Array[ResolvedConst]

rterm_to_string

rterm_to_string は解決済み項を整形する。定数は RConst(name#id : inst <= schema) の形で出力される。

pub fn rterm_to_string(RTerm) -> String

解決

elab_resolve_name

elab_resolve_name(state, ctx, name) は名前を解決する。ctx にローカルがあればそれに、なければ state で可視な定数をそのスキーマ型で解決する。ローカルは定数より優先される。

pub fn elab_resolve_name(@kernel.KernelState, ElabCtx, String) -> Result[RTerm, ElabError]

名前がどちらでもない場合は UnknownName(name) で失敗する。

elab_resolve_const、elab_resolve_const_by_name、elab_resolve_const_instance

これらの関数は定数を解決する。elab_resolve_const(state, name, ty) は型 ty での出現を解決し、elab_resolve_const_by_name はスキーマ型を用い、elab_resolve_const_instance は結果を RConst で包む。

pub fn elab_resolve_const(@kernel.KernelState, String, @kernel.HolType) -> Result[ResolvedConst, ElabError]
pub fn elab_resolve_const_by_name(@kernel.KernelState, String) -> Result[ResolvedConst, ElabError]
pub fn elab_resolve_const_instance(@kernel.KernelState, String, @kernel.HolType) -> Result[RTerm, ElabError]

名前 = は常に組み込みの等号に解決される。失敗は次のとおり。何も宣言されていなければ UnknownConst(name)、型がスキーマのインスタンスでなければ InvalidConstInstance(name)、状態の同一性テーブルとスキーマテーブルが食い違っていれば ScopeResolutionMismatch。

elab_resolve_var

elab_resolve_var は文脈を参照せずに解決済みの変数を構築する。

pub fn elab_resolve_var(String, @kernel.HolType) -> RTerm

elab_resolve_app、elab_resolve_abs、elab_resolve_eq

これらの関数は、解決済みの部品から適用、抽象、等式を構築し、結果を状態と文脈に対して型検査する。

pub fn elab_resolve_app(@kernel.KernelState, ElabCtx, RTerm, RTerm) -> Result[RTerm, ElabError]
pub fn elab_resolve_abs(@kernel.KernelState, ElabCtx, String, @kernel.HolType, RTerm) -> Result[RTerm, ElabError]
pub fn elab_resolve_eq(@kernel.KernelState, ElabCtx, RTerm, RTerm) -> Result[RTerm, ElabError]

結果が型付けできない場合は CoreTypingFailure で失敗する。elab_resolve_abs では、本体は束縛子で拡張した ctx で検査される。

elab_resolve_named_term と elab_roundtrip_term

elab_resolve_named_term(state, ctx, t) は既存のカーネル項を解決する。自由変数は同じ型の ctx のローカルでなければならず、定数は state で名前により改めて検索される。elab_roundtrip_term はさらに結果をローワリングし、定数の同一性を含めて t と α 同値であることを検査する。

pub fn elab_resolve_named_term(@kernel.KernelState, ElabCtx, @kernel.Term) -> Result[RTerm, ElabError]
pub fn elab_roundtrip_term(@kernel.KernelState, ElabCtx, @kernel.Term) -> Result[RTerm, ElabError]

t の定数が別の同一性に解決されるようになった場合、elab_roundtrip_term は ScopeResolutionMismatch で失敗する。これは、あるスコープで構築された項が、シャドーイングする宣言の後で検出される仕組みである。

test "resolve" {
  let a = @kernel.mk_tyvar("A")
  let st = @kernel.ks_add_const(@kernel.empty_kernel_state(), "c", a).unwrap()
  let ctx = @elab.elab_ctx_from_locals([("x", a)])
  let x = @elab.elab_resolve_name(st, ctx, "x").unwrap()
  let c = @elab.elab_resolve_name(st, ctx, "c").unwrap()
  inspect(@elab.rterm_to_string(c), content="RConst(c#1 : A <= A)")
  let eq = @elab.elab_resolve_eq(st, ctx, x, c).unwrap()
  inspect(@elab.elab_check_core_type(st, ctx, eq, @kernel.bool_ty()), content="true")
  inspect(@elab.elab_resolve_name(st, ctx, "nope") is Err(@elab.UnknownName("nope")), content="true")
  // a local named like a constant wins
  let ctx2 = @elab.elab_ctx_extend(ctx, "c", @kernel.bool_ty())
  inspect(@elab.elab_resolve_name(st, ctx2, "c") is Ok(@elab.RVar("c", _)), content="true")
}

コア型付け

elab_core_type_of、elab_check_core_type、rterm_well_formed

elab_core_type_of(state, ctx, t) は解決済み項の型を計算する。変数は同じ型の ctx のローカルでなければならず、定数は state で同じ同一性とスキーマを持ち続けていて、その型がスキーマのインスタンスでなければならない。elab_check_core_type は結果を期待される型と比較し、rterm_well_formed は型が存在するかを検査する。

pub fn elab_core_type_of(@kernel.KernelState, ElabCtx, RTerm) -> @kernel.HolType?
pub fn elab_check_core_type(@kernel.KernelState, ElabCtx, RTerm, @kernel.HolType) -> Bool
pub fn rterm_well_formed(@kernel.KernelState, ElabCtx, RTerm) -> Bool

コア型付けは名前を再検索しない。凍結された同一性がもはや有効なものでなければ、新しい定数を拾うのではなく型付けが失敗する。

ローワリング

elab_lower_to_term と elab_lower_to_db

elab_lower_to_term は解決済み項を、すべての定数の同一性を保ったままカーネル項に変換する。elab_lower_to_db はさらにカーネルの de Bruijn 形式まで進める。

pub fn elab_lower_to_term(RTerm) -> Result[@kernel.Term, ElabError]
pub fn elab_lower_to_db(RTerm) -> Result[@kernel.DbTerm, ElabError]

どちらも、ローワリングされた項が型付けできない場合や変換できない場合に CoreTypingFailure で失敗する。

test "freeze" {
  let bool = @kernel.bool_ty()
  let st1 = @kernel.ks_add_const(@kernel.empty_kernel_state(), "c", bool).unwrap()
  let ctx = @elab.empty_elab_ctx()
  let c1 = @elab.elab_resolve_name(st1, ctx, "c").unwrap()
  // a shadowing declaration in an inner scope
  let st2 = @kernel.ks_add_const(@kernel.ks_push_scope(st1), "c", bool).unwrap()
  inspect(@elab.rterm_well_formed(st1, ctx, c1), content="true")
  inspect(@elab.rterm_well_formed(st2, ctx, c1), content="false")
  // the lowered term keeps the identity resolved in st1
  let t = @elab.elab_lower_to_term(c1).unwrap()
  inspect(@kernel.term_to_string(t), content="Const(c#1 : bool)")
  inspect(@elab.elab_roundtrip_term(st2, ctx, t) is Err(@elab.ScopeResolutionMismatch), content="true")
}

カーネル項ビルダー

これらの関数は、解決済み項を必要としない呼び出し側のために、型検査付きでカーネルの Term 値を直接構築する。

elab_var、elab_const、elab_const_instance

elab_var は mk_var である。elab_const と elab_const_instance は、それぞれ ks_mk_const と ks_mk_const_instance である。

pub fn elab_var(String, @kernel.HolType) -> @kernel.Term
pub fn elab_const(@kernel.KernelState, String) -> Result[@kernel.Term, @kernel.SigError]
pub fn elab_const_instance(@kernel.KernelState, String, @kernel.HolType) -> Result[@kernel.Term, @kernel.SigError]

elab_app、elab_app2、elab_abs、elab_eq

これらの関数は適用(引数 1 つまたは 2 つ)、抽象、等式を構築し、結果が型付けできない場合は TypeMismatch で失敗する。

pub fn elab_app(@kernel.Term, @kernel.Term) -> Result[@kernel.Term, @kernel.LogicError]
pub fn elab_app2(@kernel.Term, @kernel.Term, @kernel.Term) -> Result[@kernel.Term, @kernel.LogicError]
pub fn elab_abs(String, @kernel.HolType, @kernel.Term) -> Result[@kernel.Term, @kernel.LogicError]
pub fn elab_eq(@kernel.Term, @kernel.Term) -> Result[@kernel.Term, @kernel.LogicError]

elab_is_prop と elab_require_prop

elab_is_prop は項が型付けされた命題かどうかを検査する。elab_require_prop は項を返すか、NotBoolTerm で失敗する(型付けできない場合は TypeMismatch)。

pub fn elab_is_prop(@kernel.Term) -> Bool
pub fn elab_require_prop(@kernel.Term) -> Result[@kernel.Term, @kernel.LogicError]
test "builders" {
  let a = @kernel.mk_tyvar("A")
  let f = @elab.elab_var("f", @kernel.fun_ty(a, @kernel.bool_ty()))
  let x = @elab.elab_var("x", a)
  let fx = @elab.elab_app(f, x).unwrap()
  inspect(@elab.elab_is_prop(fx), content="true")
  inspect(@elab.elab_app(x, f) is Err(@kernel.TypeMismatch), content="true")
  inspect(@elab.elab_require_prop(x) is Err(@kernel.NotBoolTerm), content="true")
}

エラー

ElabError

ElabError は解決とローワリングのエラーである。

pub enum ElabError {
  UnknownName(String)
  UnknownConst(String)
  InvalidConstInstance(String)
  ScopeResolutionMismatch
  CoreTypingFailure
}
コンストラクタ意味
UnknownName(name)名前がローカルでも可視な定数でもない。
UnknownConst(name)この名前の定数は宣言されていない。
InvalidConstInstance(name)要求された型が定数のスキーマのインスタンスではない。
ScopeResolutionMismatch凍結された同一性が状態と一致しなくなった、または状態のテーブルが食い違っている。
CoreTypingFailure解決済み項が、現在の状態と文脈で型付けできない。

elab_err_unknown_name、elab_err_unknown_const、elab_err_invalid_const_instance、elab_err_scope_resolution_mismatch、elab_err_core_typing_failure

これらの述語は、ElabError がどのコンストラクタであるかを検査する。パターンよりも関数を好む呼び出し側向けである。

pub fn elab_err_unknown_name(ElabError) -> Bool
pub fn elab_err_unknown_const(ElabError) -> Bool
pub fn elab_err_invalid_const_instance(ElabError) -> Bool
pub fn elab_err_scope_resolution_mismatch(ElabError) -> Bool
pub fn elab_err_core_typing_failure(ElabError) -> Bool