Lambda's Factory #
This module formalizes Lambda's factory, which is a mechanism to extend the
type checker (see Strata.DL.Lambda.LExprT) and partial evaluator (see
Strata.DL.Lambda.LExprEval) by providing a map from operations to their types
and optionally, denotations. The factory allows adding type checking and
evaluation support for new operations without modifying the implementation of
either or any core ASTs.
Also see Strata.DL.Lambda.IntBoolFactory for a concrete example of a factory.
A signature is a map from variable identifiers to types.
Equations
- Lambda.Signature IDMeta Ty = ListMap (Lambda.Identifier IDMeta) Ty
Instances For
Equations
- Lambda.Signature.format [] = Std.Format.text ""
- Lambda.Signature.format [(k, v)] = Std.format "(" ++ Std.format k ++ Std.format " : " ++ Std.format v ++ Std.format ")"
- Lambda.Signature.format ((k, v) :: rest) = Std.format "(" ++ Std.format k ++ Std.format " : " ++ Std.format v ++ Std.format ") " ++ Lambda.Signature.format rest
Instances For
Equations
Instances For
Equations
Instances For
The AST-facing Lambda function structure - instantiation of the base Func for
Lambda expressions. It is used for functions that appear in the Strata AST
(e.g. Core.Function, funcDecl), and carries only plain data — the base
Func excludes the function-typed concreteEval, so it has decidable equality.
Universally quantified type identifiers, if any, appear before this signature and can quantify over the type identifiers in it.
Equations
Instances For
A Lambda factory function - the full, evaluator/factory-facing function
structure. It extends the AST-facing Func with the partial-evaluator hook
concreteEval. All other fields live on the base Func.
A optional evaluation function can be provided in the concreteEval field for
each factory function to allow the partial evaluator to do constant propagation
when all the arguments of a function are concrete. Such a function should take
two inputs: a function call expression and also -- somewhat redundantly, but
perhaps more conveniently -- the list of arguments in this expression. Here's
an example of a concreteEval function for Int.Add:
(fun e args => match args with
| [e1, e2] =>
let e1i := LExpr.denoteInt e1
let e2i := LExpr.denoteInt e2
match e1i, e2i with
| some x, some y => (.const (toString (x + y)) mty[int])
| _, _ => e
| _ => e)
Note that if there is an arity mismatch or if the arguments are not concrete/constants, this fails and it returns .none. If LFunc already has body, it must not have concreteEval, and vice versa.
- mk' :: (
- name : T.Identifier
- body : Option (Lambda.LExpr T.mono)
- axioms : List (Lambda.LExpr T.mono)
- preconditions : List (FuncPrecondition (Lambda.LExpr T.mono) T.Metadata)
- measure : Option (Lambda.LExpr T.mono)
- )
Instances For
Helper constructor for LFunc to maintain backward compatibility.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Lift an AST-facing LFuncDefined (base Func) into the full LFunc,
optionally attaching concreteEval at the evaluator boundary. All other
data carries over unchanged via toFunc.
Instances For
Equations
- Lambda.instInhabitedLFunc = { default := { name := default, inputs := [], output := Lambda.LMonoTy.bool } }
Equations
- Lambda.instToFormatLFuncOfLExpr = { format := fun (f : Lambda.LFunc T) => f.format }
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Equations
- f.inputPolyTypes = List.map (fun (x : T.Identifier × Lambda.LMonoTy) => match x with | (id, mty) => (id, Lambda.LTy.forAll f.typeArgs mty)) f.inputs
Instances For
Equations
- f.inputMonoSignature = List.map (fun (x : T.Identifier × Lambda.LMonoTy) => match x with | (id, mty) => (id, Lambda.LTy.forAll [] mty)) f.inputs
Instances For
Equations
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Equations
Instances For
Equations
Instances For
Equations
Instances For
Equations
Instances For
Equations
- f.eraseTypes = { toFunc := Lambda.LFuncDefined.eraseTypes f.toFunc, concreteEval := f.concreteEval }
Instances For
The type checker and partial evaluator for Lambda is parameterizable by
a user-provided Factory.
We don't have any "built-in" functions like +, -, etc. in (LExpr IDMeta) -- lambdas are our only tool. Factory gives us a way to add
support for concrete/symbolic evaluation and type checking for FunFactory
functions without actually modifying any core logic or the ASTs.
The underlying array of factory functions.
- nameMap : Std.HashMap String Nat
- nameMapValid {k : String} (p : k ∈ Lambda.Factory.nameMap✝ self) : (Lambda.Factory.nameMap✝¹ self)[k] < self.toArray.size
Instances For
Equations
- f.mem name = (name ∈ Lambda.Factory.nameMap✝ f)
Instances For
Equations
- f.instMemDecidable name = inferInstance
Instances For
Equations
- Lambda.Factory.instMem = { mem := Lambda.Factory.mem }
Equations
- f.instMembershipDecidable name = f.instMemDecidable name
Instances For
Equations
- Lambda.Factory.instGetElem? = { getElem := Lambda.Factory.get, getElem? := Lambda.Factory.get? }
Equations
Instances For
Equations
- Lambda.Factory.instInhabited = { default := Lambda.Factory.default }
Equations
Instances For
Equations
- F.append a = Array.foldl Lambda.Factory.pushIfNew F a
Instances For
Equations
Instances For
Equations
- F.getFunctionNames = Array.map (fun (f : Lambda.LFunc T) => f.name) F.toArray
Instances For
Add a function func to the factory F. Redefinitions are not allowed.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Append a factory newF to an existing factory F, checking for redefinitions
along the way.
Equations
- F.tryAddAll newF = Array.foldlM (fun (x1 : Lambda.Factory T) (x2 : Lambda.LFunc T) => x1.tryPush x2) F newF
Instances For
Append a factory newF to an existing factory F, checking for redefinitions
along the way.
Equations
- F.addFactory newF = F.tryAddAll newF.toArray
Instances For
Equations
Instances For
Equations
- Lambda.getLFuncCall.go (Lambda.LExpr.app m (Lambda.LExpr.app m_1 e' arg1) arg2) acc = Lambda.getLFuncCall.go e' ([arg1, arg2] ++ acc)
- Lambda.getLFuncCall.go (Lambda.LExpr.app m (Lambda.LExpr.op m_1 fn fnty) arg1) acc = (Lambda.LExpr.op m_1 fn fnty, [arg1] ++ acc)
- Lambda.getLFuncCall.go e acc = (e, acc)
Instances For
Equations
Instances For
If e is a call of a factory function, get the operator (.op), a list
of all the actuals, and the (LFunc IDMeta).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Apply type substitution S to all type annotations in an LExpr.
This is only for user-defined types, not metadata-stored resolved types.
If e is an LExprT whose metadata contains type information, use applySubstT.
Equations
- e.applySubst S = if S.hasEmptyScopes = true then e else e.replaceUserProvidedType (Lambda.LMonoTy.subst S)
Instances For
Best-effort type extraction from an LExpr without a typing context.
Returns none when the type cannot be determined syntactically.
Equations
- (Lambda.LExpr.const m c).typeOf = some c.ty
- (Lambda.LExpr.op m o ty).typeOf = ty
- (Lambda.LExpr.bvar m deBruijnIndex).typeOf = none
- (Lambda.LExpr.fvar m name ty).typeOf = ty
- (Lambda.LExpr.abs m prettyName (some argTy) e).typeOf = Option.map (fun (x : Lambda.LMonoTy) => Lambda.LMonoTy.arrow argTy x) e.typeOf
- (Lambda.LExpr.abs m prettyName none e).typeOf = none
- (Lambda.LExpr.quant m k prettyName ty trigger e).typeOf = some Lambda.LMonoTy.bool
- (Lambda.LExpr.app m fn e).typeOf = fn.typeOf.bind fun (x : Lambda.LMonoTy) => match x with | Lambda.LMonoTy.tcons "arrow" [head, ret] => some ret | x => none
- (Lambda.LExpr.ite m c t e).typeOf = t.typeOf
- (Lambda.LExpr.eq m e1 e2).typeOf = some Lambda.LMonoTy.bool
Instances For
Derive a type substitution from the .op type annotation alone, by unifying it
against the function's generic type. On annotated terms (i.e., terms that have
undergone type inference), the .op node always carries a type annotation, so
this suffices.
Returns some Subst.empty when fn.typeArgs is empty (monomorphic — no-op).
Returns none if the callee is not annotated or unification fails.
Equations
Instances For
Derive a type substitution by unifying the instantiated operator type against the function's generic type. Used when inlining a polymorphic function body to instantiate type variables.
Prefers the .op annotation (via opTypeSubst). Falls back to a best-effort
approach using argument types when the .op is not annotated. On annotated terms
(after type inference), the .op always carries a type annotation, so the fallback
is never needed.
Returns some Subst.empty when fn.typeArgs is empty (monomorphic — no-op).
Returns none if the type substitution cannot be derived.
Equations
- One or more equations did not get rendered due to their size.