WIP
variable (d : Dialect) [DialectSignature d] instantiates the core data structure (Expr, Com), it is intrinsically well typed: typing rules are baked into the data structure,DialectSignature: is a map from operations to the signature/-- An intrinsically typed expression whose effect is *at most* EffectKind -/
inductive Expr : (Γ : Ctxt d.Ty) → (eff : EffectKind) → (ty : d.Ty) → Type where
  | mk {Γ} {ty} (op : d.Op)
    (ty_eq : ty = DialectSignature.outTy op)
    (eff_le : DialectSignature.effectKind op ≤ eff)
    (args : HVector (Var Γ) <| DialectSignature.sig op) 
    /- For now, assume that regions are impure.
       We keep it this way to minimize the total amount of disruption in our definitions.
       We shall change this once the rest of the file goes through. -/
    (regArgs : HVector (fun t : Ctxt d.Ty × d.Ty => Com t.1 .impure t.2)
      (DialectSignature.regSig op)) : Expr Γ eff ty
where:
(Γ : Ctxt d.Ty) = dialect types(ty : d.Ty) dialect type expected for this expressionx : Expr [.int, .int] _ .int[.int, .int] is the context (usually Γ), declares the type of the free varuiables, where .int is defined within the dialect only.int is the expected return type (again within the dialect)x is well typed with free vars [.int, .int] and is of type int(Γ : Ctxt d.Ty) the argument has the type it is supposed to haveA note about HVectors:
HVector : (f : α → Type) → List α → Type is to store types in a data struc (e.g. α), this must be in a higher universe (universe bump). HVector does not strictly store types, but rather elements of α, meaning that we don’t have to perform the universe bump.α is an encoding for a type, but not a type to avoid universe problemsf allows us to interpret α as a type (“denotation before types”)HVecstorsL: one for the input and one for the region HVector toType [bool] → HVector _ [ ([ ], int)] → toType int("context" := Valuation.region.1), ("returnType" := [[region.2]]), fun region => Valuation.region.1 → [[region.2]]NB: context declares free vars and their types
Variables/valuation/denotation notes:
v : Γ.Var ty: v is a variable in context Γ of type tyVar Γ : d.Ty → Type “f”DialectSignature.sig op : List d.Ty as : List d.Tyf as[i]∀ ty Γ.Var.ty → [[ty]]toType : [[ty]] = toType ty : Type (Lean type corresponding to our own types we define in the dialect)Semantics notes:
DialectDenote.xs[i] : ToType argumentTypes[i]
xs[i] : BitVec 32
{(int) → int}
BitVec 32 → BitVec 32
[[int]] = BitVec 32 “denotation of int is bitvec 32”BitVec 32 → BitVec 32 → BitVec 32 generically, giving the denotation of an arbitraty something (?) “curried function”Currying:
HVector toType (DialectSignature.sig add → toType (DialectSignature.outTy ty))α → β → γ (curried)  is the same as f (α, β) → γ (uncurried), they are equivalentλy.f(x,y) allows to move from curry to uncurryγ ^ (α × β) ≃ (γ ^ β) ^ α, (α × β) → γ ≃ α → (β → γ)toType:
[[arg[0]]] → [[arg[1]]] → ... → [[arg[n]]] → [[retType]]
HVector def remains first order for this reason)example:
{( ) → int} → (bool) → int
DialectSignature.sig if = [bool]
DialectSignature.outTy if = int
DialectSignature.regSig if = [ ([ ], int) ]
(Unit → BitVec 32) → Bool → BitVec 32(Unit → BitVec 32) because of Lean’s eagnerness (we make it lazy)HVector