hydra-0.13.0: src/main/haskell/Hydra/Sources/CoderUtils.hs
-- | Common utilities for language coders, providing shared patterns for term decomposition,
-- environment management, and other cross-language concerns.
--
-- This module is expressed in the Hydra DSL and generates code for all target implementations.
module Hydra.Sources.CoderUtils where
-- Standard imports for kernel terms modules
import Hydra.Kernel hiding (
commentsFromElement,
commentsFromFieldType,
gatherApplications,
gatherArgs,
gatherArgsWithTypeApps,
isComplexBinding,
isComplexTerm,
isComplexVariable,
isSimpleAssignment,
isTrivialTerm,
normalizeComment)
import Hydra.Sources.Libraries
import qualified Hydra.Dsl.Annotations as Annotations
import qualified Hydra.Dsl.Meta.Ast as Ast
import qualified Hydra.Dsl.Bootstrap as Bootstrap
import qualified Hydra.Dsl.Meta.Coders as Coders
import qualified Hydra.Dsl.Meta.Compute as Compute
import qualified Hydra.Dsl.Meta.Core as Core
import qualified Hydra.Dsl.Meta.Grammar as Grammar
import qualified Hydra.Dsl.Grammars as Grammars
import qualified Hydra.Dsl.Meta.Graph as Graph
import qualified Hydra.Dsl.Meta.Json as Json
import qualified Hydra.Dsl.Meta.Lib.Chars as Chars
import qualified Hydra.Dsl.Meta.Lib.Eithers as Eithers
import qualified Hydra.Dsl.Meta.Lib.Equality as Equality
import qualified Hydra.Dsl.Meta.Lib.Flows as Flows
import qualified Hydra.Dsl.Meta.Lib.Lists as Lists
import qualified Hydra.Dsl.Meta.Lib.Literals as Literals
import qualified Hydra.Dsl.Meta.Lib.Logic as Logic
import qualified Hydra.Dsl.Meta.Lib.Maps as Maps
import qualified Hydra.Dsl.Meta.Lib.Math as Math
import qualified Hydra.Dsl.Meta.Lib.Maybes as Maybes
import qualified Hydra.Dsl.Meta.Lib.Pairs as Pairs
import qualified Hydra.Dsl.Meta.Lib.Sets as Sets
import Hydra.Dsl.Meta.Lib.Strings as Strings
import qualified Hydra.Dsl.Literals as Literals
import qualified Hydra.Dsl.LiteralTypes as LiteralTypes
import qualified Hydra.Dsl.Meta.Base as MetaBase
import qualified Hydra.Dsl.Meta.Terms as MetaTerms
import qualified Hydra.Dsl.Meta.Types as MetaTypes
import qualified Hydra.Dsl.Meta.Module as Module
import Hydra.Dsl.Meta.Phantoms as Phantoms
import qualified Hydra.Dsl.Prims as Prims
import qualified Hydra.Dsl.Tabular as Tabular
import qualified Hydra.Dsl.Meta.Testing as Testing
import qualified Hydra.Dsl.Terms as Terms
import qualified Hydra.Dsl.Tests as Tests
import qualified Hydra.Dsl.Meta.Topology as Topology
import qualified Hydra.Dsl.Types as Types
import qualified Hydra.Dsl.Meta.Typing as Typing
import qualified Hydra.Dsl.Meta.Util as Util
import qualified Hydra.Dsl.Meta.Variants as Variants
import Hydra.Sources.Kernel.Types.All
import Prelude hiding ((++))
import qualified Data.Int as I
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Maybe as Y
import qualified Hydra.Sources.Kernel.Terms.Annotations as Annotations
import qualified Hydra.Sources.Kernel.Terms.Arity as Arity
import qualified Hydra.Sources.Kernel.Terms.Checking as Checking
import qualified Hydra.Sources.Kernel.Terms.Formatting as Formatting
import qualified Hydra.Sources.Kernel.Terms.Lexical as Lexical
import qualified Hydra.Sources.Kernel.Terms.Monads as Monads
import qualified Hydra.Sources.Kernel.Terms.Rewriting as Rewriting
import qualified Hydra.Sources.Kernel.Terms.Schemas as Schemas
ns :: Namespace
ns = Namespace "hydra.coderUtils"
define :: String -> TTerm a -> TBinding a
define = definitionInNamespace ns
module_ :: Module
module_ = Module ns elements
[Annotations.ns, Arity.ns, Checking.ns, Lexical.ns, Monads.ns, Rewriting.ns, Schemas.ns]
kernelTypesNamespaces $
Just "Common utilities for language coders, providing shared patterns for term decomposition and analysis."
where
elements = [
-- Simple pure functions first
toBinding normalizeComment,
toBinding gatherApplications,
toBinding gatherArgs,
toBinding gatherArgsWithTypeApps,
-- Predicates
toBinding isSimpleAssignment,
toBinding isComplexTerm,
toBinding isComplexVariable,
toBinding isComplexBinding,
toBinding isTrivialTerm,
-- Tail-call optimization detection
toBinding isSelfTailRecursive,
toBinding isTailRecursiveInTailPosition,
-- Flow-based utilities
toBinding commentsFromElement,
toBinding commentsFromFieldType,
-- Function analysis helpers
toBinding tryTypeOf,
toBinding bindingMetadata,
toBinding analyzeFunctionTermWith_finish,
toBinding analyzeFunctionTermWith_gather,
toBinding analyzeFunctionTermWith,
toBinding analyzeFunctionTermNoInferWith_finish,
toBinding analyzeFunctionTermNoInferWith_gather,
toBinding analyzeFunctionTermNoInferWith,
toBinding analyzeFunctionTerm,
toBinding analyzeFunctionTermInline,
toBinding analyzeFunctionTermNoInfer,
-- State management helpers for coders
toBinding updateCoderMetadata,
toBinding withUpdatedCoderGraph,
toBinding withGraphBindings,
toBinding inCoderGraphContext]
--------------------------------------------------------------------------------
-- Simple pure functions
--------------------------------------------------------------------------------
-- | Normalize a comment string for consistent output across coders.
-- Strips leading/trailing whitespace and ensures the comment ends with a period.
-- Returns empty string for whitespace-only input.
normalizeComment :: TBinding (String -> String)
normalizeComment = define "normalizeComment" $
doc "Normalize a comment string for consistent output across coders" $
"s" ~>
"stripped" <~ Formatting.stripLeadingAndTrailingWhitespace @@ var "s" $
Logic.ifElse
(Strings.null (var "stripped"))
(string "")
-- Get the last character by using charAt with (length - 1)
-- Code point 46 is '.'
("lastIdx" <~ Math.sub (Strings.length (var "stripped")) (int32 1) $
"lastChar" <~ Strings.charAt (var "lastIdx") (var "stripped") $
Logic.ifElse
(Equality.equal (var "lastChar") (int32 46))
(var "stripped")
(Strings.cat2 (var "stripped") (string ".")))
-- | Recursively gather applications from a term, returning the list of arguments
-- and the base term. Applications are traversed left-to-right, with arguments
-- collected in the order they appear (leftmost first).
--
-- For example, given a term representing @f a b c@, this returns @([a, b, c], f)@.
gatherApplications :: TBinding (Term -> ([Term], Term))
gatherApplications = define "gatherApplications" $
doc "Gather applications from a term, returning (args, baseTerm)" $
"term" ~>
-- Use a local recursive helper with an accumulator
"go" <~ ("args" ~> "t" ~>
cases _Term (Rewriting.deannotateTerm @@ var "t")
(Just $ pair (var "args") (var "t")) [
_Term_application>>: "app" ~>
"lhs" <~ Core.applicationFunction (var "app") $
"rhs" <~ Core.applicationArgument (var "app") $
var "go" @@ (Lists.cons (var "rhs") (var "args")) @@ var "lhs"]) $
var "go" @@ (list ([] :: [TTerm Term])) @@ var "term"
-- | Recursively gather applications, type lambdas, and type applications from a term.
-- Returns a pair of (base term, argument list) where the base term has all applications,
-- type lambdas, and type applications removed.
gatherArgs :: TBinding (Term -> [Term] -> (Term, [Term]))
gatherArgs = define "gatherArgs" $
doc "Gather term arguments, stripping type-level constructs" $
"term" ~> "args" ~>
cases _Term (Rewriting.deannotateTerm @@ var "term")
(Just $ pair (var "term") (var "args")) [
_Term_application>>: "app" ~>
"lhs" <~ Core.applicationFunction (var "app") $
"rhs" <~ Core.applicationArgument (var "app") $
gatherArgs @@ var "lhs" @@ (Lists.cons (var "rhs") (var "args")),
_Term_typeLambda>>: "tl" ~>
"body" <~ Core.typeLambdaBody (var "tl") $
gatherArgs @@ var "body" @@ var "args",
_Term_typeApplication>>: "ta" ~>
"body" <~ Core.typeApplicationTermBody (var "ta") $
gatherArgs @@ var "body" @@ var "args"]
-- | Like gatherArgs but also collects type arguments from TermTypeApplication nodes.
-- Returns (fun, args, typeArgs) where typeArgs are in application order.
gatherArgsWithTypeApps :: TBinding (Term -> [Term] -> [Type] -> (Term, [Term], [Type]))
gatherArgsWithTypeApps = define "gatherArgsWithTypeApps" $
doc "Gather term and type arguments from a term" $
"term" ~> "args" ~> "tyArgs" ~>
cases _Term (Rewriting.deannotateTerm @@ var "term")
(Just $ triple (var "term") (var "args") (var "tyArgs")) [
_Term_application>>: "app" ~>
"lhs" <~ Core.applicationFunction (var "app") $
"rhs" <~ Core.applicationArgument (var "app") $
gatherArgsWithTypeApps @@ var "lhs" @@ (Lists.cons (var "rhs") (var "args")) @@ var "tyArgs",
_Term_typeLambda>>: "tl" ~>
"body" <~ Core.typeLambdaBody (var "tl") $
gatherArgsWithTypeApps @@ var "body" @@ var "args" @@ var "tyArgs",
_Term_typeApplication>>: "ta" ~>
"body" <~ Core.typeApplicationTermBody (var "ta") $
"typ" <~ Core.typeApplicationTermType (var "ta") $
gatherArgsWithTypeApps @@ var "body" @@ var "args" @@ (Lists.cons (var "typ") (var "tyArgs"))]
--------------------------------------------------------------------------------
-- Predicate functions
--------------------------------------------------------------------------------
-- | Determines whether a term can be encoded as a simple assignment (without type annotation).
-- A term is considered a simple assignment if:
-- - It's not a lambda, let, or type lambda
-- - It's not a type application (which introduces polymorphism requiring type signatures)
-- - When peeled of applications, it's not a case statement
isSimpleAssignment :: TBinding (Term -> Bool)
isSimpleAssignment = define "isSimpleAssignment" $
doc "Check if a term can be encoded as a simple assignment" $
"term" ~>
cases _Term (var "term")
(Just $
-- Check if the base term (after gathering args) is a union elimination
"baseTerm" <~ Pairs.first (gatherArgs @@ var "term" @@ list ([] :: [TTerm Term])) $
cases _Term (var "baseTerm")
(Just $ boolean True) [
_Term_function>>: "f" ~>
cases _Function (var "f")
(Just $ boolean True) [
_Function_elimination>>: "elim" ~>
cases _Elimination (var "elim")
(Just $ boolean True) [
_Elimination_union>>: constant (boolean False)]]]) [
_Term_annotated>>: "at" ~>
isSimpleAssignment @@ (Core.annotatedTermBody $ var "at"),
_Term_function>>: "f" ~>
cases _Function (var "f")
(Just $ boolean True) [
_Function_lambda>>: constant (boolean False)],
_Term_let>>: constant (boolean False),
_Term_typeLambda>>: constant (boolean False),
_Term_typeApplication>>: "ta" ~>
isSimpleAssignment @@ (Core.typeApplicationTermBody $ var "ta")]
-- | Determine whether a given term needs to be treated as a (possibly nullary) function,
-- rather than a simple value. The term might be an actual function, or it may have type parameters
-- or internal let bindings, or it may reference complex variables.
isComplexTerm :: TBinding (TypeContext -> Term -> Bool)
isComplexTerm = define "isComplexTerm" $
doc "Check if a term needs to be treated as a function rather than a simple value" $
"tc" ~> "t" ~>
cases _Term (var "t")
(Just $
-- Default: check if any subterm is complex
Lists.foldl
("b" ~> "sub" ~> Logic.or (var "b") (isComplexTerm @@ var "tc" @@ var "sub"))
(boolean False)
(Rewriting.subterms @@ var "t")) [
_Term_let>>: constant (boolean True),
_Term_typeApplication>>: constant (boolean True),
_Term_typeLambda>>: constant (boolean True),
_Term_variable>>: "name" ~> isComplexVariable @@ var "tc" @@ var "name"]
-- | Look up a variable to see if it is bound to a complex term
isComplexVariable :: TBinding (TypeContext -> Name -> Bool)
isComplexVariable = define "isComplexVariable" $
doc "Check if a variable is bound to a complex term" $
"tc" ~> "name" ~>
-- Check if there's metadata for this variable (indicates complexity)
"metaLookup" <~ Maps.lookup (var "name") (Typing.typeContextMetadata $ var "tc") $
Logic.ifElse
(Maybes.isJust (var "metaLookup"))
(boolean True)
-- Lambda-bound variables are complex because they might be thunked
(Logic.ifElse
(Sets.member (var "name") (Typing.typeContextLambdaVariables $ var "tc"))
(boolean True)
-- If not in type context, assume mutual recursion (complex)
("typeLookup" <~ Maps.lookup (var "name") (Typing.typeContextTypes $ var "tc") $
Logic.not (Maybes.isJust (var "typeLookup"))))
-- | Check if a binding is complex and needs to be treated as a function
isComplexBinding :: TBinding (TypeContext -> Binding -> Bool)
isComplexBinding = define "isComplexBinding" $
doc "Check if a binding needs to be treated as a function" $
"tc" ~> "b" ~>
"term" <~ Core.bindingTerm (var "b") $
"mts" <~ Core.bindingType (var "b") $
-- Bindings without type schemes are complex (e.g., lifted case expressions)
Maybes.cases (var "mts")
(isComplexTerm @@ var "tc" @@ var "term") $
"ts" ~>
-- Check if polymorphic
"isPolymorphic" <~ Logic.not (Lists.null (Core.typeSchemeVariables $ var "ts")) $
-- Check if non-nullary
"isNonNullary" <~ Equality.gt (Arity.typeArity @@ (Core.typeSchemeType $ var "ts")) (int32 0) $
-- Check if complex term
"isComplex" <~ isComplexTerm @@ var "tc" @@ var "term" $
Logic.or (Logic.or (var "isPolymorphic") (var "isNonNullary")) (var "isComplex")
-- | Check if a term is trivially cheap to evaluate, meaning it needs no thunking.
-- Trivial terms include: literals, plain variable references, field projections
-- on trivial terms (e.g. app.function), unit values, maybe wrappers around trivial terms,
-- and type wrappers around trivial terms.
-- Field projections cause minor regressions in inference/hoisting (~200ms) but yield
-- large gains in checking/annotations/strings (~1500ms), for a net 4.2% improvement.
-- This is a conservative predicate: anything not explicitly recognized is non-trivial.
isTrivialTerm :: TBinding (Term -> Bool)
isTrivialTerm = define "isTrivialTerm" $
doc "Check if a term is trivially cheap (no thunking needed)" $
"t" ~>
cases _Term (Rewriting.deannotateTerm @@ var "t")
(Just $ boolean False) [
-- Literals are always trivial
_Term_literal>>: constant (boolean True),
-- Plain variables are trivial (the variable itself is cheap to reference)
_Term_variable>>: constant (boolean True),
-- Unit is trivial
_Term_unit>>: constant (boolean True),
-- Field projection on a trivial subterm is trivial (e.g. app.function)
_Term_application>>: "app" ~>
"fun" <~ Core.applicationFunction (var "app") $
"arg" <~ Core.applicationArgument (var "app") $
cases _Term (var "fun") (Just $ boolean False) [
_Term_function>>: "f" ~>
cases _Function (var "f") (Just $ boolean False) [
_Function_elimination>>: "e" ~>
cases _Elimination (var "e") (Just $ boolean False) [
-- record projection: trivial if the subject is trivial
_Elimination_record>>: constant (isTrivialTerm @@ var "arg")]]],
-- Maybe term (just x) where x is trivial; nothing is also trivial
_Term_maybe>>: "opt" ~>
Maybes.maybe (boolean True) ("inner" ~> isTrivialTerm @@ var "inner") (var "opt"),
-- Type applications/lambdas: check the inner term
_Term_typeApplication>>: "ta" ~> isTrivialTerm @@ (Core.typeApplicationTermBody $ var "ta"),
_Term_typeLambda>>: "tl" ~> isTrivialTerm @@ (Core.typeLambdaBody $ var "tl")]
--------------------------------------------------------------------------------
-- Tail-call optimization detection
--------------------------------------------------------------------------------
-- | Check if a term body is self-tail-recursive with respect to a function name.
-- Returns True if the function references itself AND all self-references are in tail position.
-- Note: isFreeVariableInTerm returns True when the variable is NOT present (confusing API).
isSelfTailRecursive :: TBinding (Name -> Term -> Bool)
isSelfTailRecursive = define "isSelfTailRecursive" $
doc "Check if a term body is self-tail-recursive with respect to a function name" $
"funcName" ~> "body" ~>
-- isFreeVariableInTerm returns True when v is NOT free (not present).
-- So Logic.not means: the name IS present as a free variable.
"callsSelf" <~ Logic.not (Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "body") $
Logic.ifElse (var "callsSelf")
(isTailRecursiveInTailPosition @@ var "funcName" @@ var "body")
false
-- | Check that all occurrences of funcName in a term are in tail position.
-- Called after confirming funcName IS present in the term.
-- Returns True if the term is safe for TCO transformation.
isTailRecursiveInTailPosition :: TBinding (Name -> Term -> Bool)
isTailRecursiveInTailPosition = define "isTailRecursiveInTailPosition" $
doc "Check that all self-references are in tail position" $
"funcName" ~> "term" ~>
"stripped" <~ (Rewriting.deannotateAndDetypeTerm @@ var "term") $
cases _Term (var "stripped") (Just $
-- Default: funcName must NOT appear free in this term (not a recognized tail position)
Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "term") [
-- Application: check if it's a self-tail-call or a case statement application
_Term_application>>: "app" ~>
"gathered" <~ (gatherApplications @@ var "stripped") $
"gatherArgs" <~ (Pairs.first $ var "gathered") $
"gatherFun" <~ (Pairs.second $ var "gathered") $
"strippedFun" <~ (Rewriting.deannotateAndDetypeTerm @@ var "gatherFun") $
cases _Term (var "strippedFun") (Just $
-- Unknown function form: funcName must not appear anywhere
Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "term") [
-- Variable: check if self-call
_Term_variable>>: "vname" ~>
Logic.ifElse (Equality.equal (var "vname") (var "funcName"))
-- Self-call in tail position: args must not contain funcName
-- and must not contain lambdas (closures over parameters break TCO
-- because Python closures capture by reference, not by value)
("argsNoFunc" <~ (Lists.foldl
("ok" ~> "arg" ~>
Logic.and (var "ok")
(Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "arg"))
true
(var "gatherArgs")) $
"argsNoLambda" <~ (Lists.foldl
("ok" ~> "arg" ~>
Logic.and (var "ok")
(Logic.not $ Rewriting.foldOverTerm @@ Coders.traversalOrderPre
@@ ("found" ~> "t" ~>
Logic.or (var "found")
(cases _Term (var "t") (Just false) [
_Term_function>>: "f2" ~>
cases _Function (var "f2") (Just false) [
_Function_lambda>>: "lam" ~>
-- Any lambda in an argument disqualifies from TCO
"ignore" <~ (Core.lambdaBody $ var "lam") $
true]]))
@@ false
@@ var "arg"))
true
(var "gatherArgs")) $
Logic.and (var "argsNoFunc") (var "argsNoLambda"))
-- Not a self-call: funcName must not appear anywhere in the term
(Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "term"),
-- Function: check for case statement (union elimination)
_Term_function>>: "f" ~>
cases _Function (var "f") (Just $
Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "term") [
_Function_elimination>>: "e" ~>
cases _Elimination (var "e") (Just $
Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "term") [
_Elimination_union>>: "cs" ~>
"cases_" <~ (Core.caseStatementCases $ var "cs") $
"dflt" <~ (Core.caseStatementDefault $ var "cs") $
-- All case branches must have funcName only in tail position
"branchesOk" <~ (Lists.foldl
("ok" ~> "field" ~>
Logic.and (var "ok")
(isTailRecursiveInTailPosition @@ var "funcName" @@ Core.fieldTerm (var "field")))
true
(var "cases_")) $
-- Default branch (if present) must also be tail-recursive
"dfltOk" <~ (Maybes.maybe true
("d" ~> isTailRecursiveInTailPosition @@ var "funcName" @@ var "d")
(var "dflt")) $
-- Arguments to the case statement must NOT contain funcName
"argsOk" <~ (Lists.foldl
("ok" ~> "arg" ~>
Logic.and (var "ok")
(Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "arg"))
true
(var "gatherArgs")) $
Logic.and (Logic.and (var "branchesOk") (var "dfltOk")) (var "argsOk")]]],
-- Lambda: tail position is the body
_Term_function>>: "f" ~>
cases _Function (var "f") (Just $
Rewriting.isFreeVariableInTerm @@ var "funcName" @@ var "term") [
_Function_lambda>>: "lam" ~>
isTailRecursiveInTailPosition @@ var "funcName" @@ (Core.lambdaBody $ var "lam")],
-- Let: tail position is the body; bindings must not contain funcName
_Term_let>>: "lt" ~>
"bindingsOk" <~ (Lists.foldl
("ok" ~> "b" ~>
Logic.and (var "ok")
(Rewriting.isFreeVariableInTerm @@ var "funcName" @@ Core.bindingTerm (var "b")))
true
(Core.letBindings $ var "lt")) $
Logic.and (var "bindingsOk")
(isTailRecursiveInTailPosition @@ var "funcName" @@ (Core.letBody $ var "lt"))]
--------------------------------------------------------------------------------
-- Flow-based utilities
--------------------------------------------------------------------------------
-- | Extract comments/description from a Binding (element definition).
-- This is a common pattern for coders that need to preserve documentation.
commentsFromElement :: TBinding (Binding -> Flow Graph (Maybe String))
commentsFromElement = define "commentsFromElement" $
doc "Extract comments/description from a Binding" $
"b" ~> Annotations.getTermDescription @@ (Core.bindingTerm $ var "b")
-- | Extract comments/description from a FieldType.
-- This is a common pattern for coders that need to preserve field documentation.
commentsFromFieldType :: TBinding (FieldType -> Flow Graph (Maybe String))
commentsFromFieldType = define "commentsFromFieldType" $
doc "Extract comments/description from a FieldType" $
"ft" ~> Annotations.getTypeDescription @@ (Core.fieldTypeType $ var "ft")
--------------------------------------------------------------------------------
-- Function analysis utilities
--------------------------------------------------------------------------------
-- | Infer the type of a term using the given TypeContext.
-- This is a helper that adds tracing for debugging purposes.
tryTypeOf :: TBinding (String -> TypeContext -> Term -> Flow s Type)
tryTypeOf = define "tryTypeOf" $
doc "Infer the type of a term with tracing" $
"msg" ~> "tc" ~> "term" ~>
Monads.withTrace @@ var "msg" @@ (Checking.typeOf @@ var "tc" @@ list ([] :: [TTerm Type]) @@ var "term")
-- | Produces a simple 'true' value if the binding is complex (needs to be treated as a function)
bindingMetadata :: TBinding (TypeContext -> Binding -> Maybe Term)
bindingMetadata = define "bindingMetadata" $
doc "Produces metadata for a binding if it is complex" $
"tc" ~> "b" ~>
Logic.ifElse
(isComplexBinding @@ var "tc" @@ var "b")
(just MetaTerms.true)
nothing
-- | Internal helper: analyze a function term with a configurable binding metadata function.
-- This is the core implementation used by all analyzeFunctionTerm variants.
--
-- The function peels off lambdas, type lambdas, lets, and type applications,
-- collecting their components into a FunctionStructure. It uses a recursive
-- gather/finish pattern with an argMode flag to track whether we're still
-- collecting lambda parameters (vs. having seen a let which stops parameter collection).
-- | Finish helper for analyzeFunctionTermWith: reapply type applications and infer return type
analyzeFunctionTermWith_finish :: TBinding (
(env -> TypeContext) ->
env -> [Name] -> [Name] -> [Binding] -> [Type] -> [Type] -> Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermWith_finish = define "analyzeFunctionTermWith_finish" $
"getTC" ~> "fEnv" ~> "tparams" ~> "args" ~> "bindings" ~> "doms" ~> "tapps" ~> "body" ~>
"bodyWithTapps" <~ Lists.foldl
("trm" ~> "typ" ~> Core.termTypeApplication (Core.typeApplicationTerm (var "trm") (var "typ")))
(var "body")
(var "tapps") $
"mcod" <<~ Flows.withDefault (nothing :: TTerm (Maybe Type))
(Flows.map (primitive _maybes_pure) (tryTypeOf @@ string "analyzeFunctionTermWith" @@ (var "getTC" @@ var "fEnv") @@ var "bodyWithTapps")) $
Flows.pure $ record _FunctionStructure [
_FunctionStructure_typeParams>>: Lists.reverse (var "tparams"),
_FunctionStructure_params>>: Lists.reverse (var "args"),
_FunctionStructure_bindings>>: var "bindings",
_FunctionStructure_body>>: var "bodyWithTapps",
_FunctionStructure_domains>>: Lists.reverse (var "doms"),
_FunctionStructure_codomain>>: var "mcod",
_FunctionStructure_environment>>: var "fEnv"]
-- | Gather helper for analyzeFunctionTermWith: recursively collect function components
analyzeFunctionTermWith_gather :: TBinding (
(TypeContext -> Binding -> Maybe Term) ->
(env -> TypeContext) ->
(TypeContext -> env -> env) ->
Bool -> env -> [Name] -> [Name] -> [Binding] -> [Type] -> [Type] -> Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermWith_gather = define "analyzeFunctionTermWith_gather" $
"forBinding" ~> "getTC" ~> "setTC" ~>
"argMode" ~> "gEnv" ~> "tparams" ~> "args" ~> "bindings" ~> "doms" ~> "tapps" ~> "t" ~>
cases _Term (Rewriting.deannotateTerm @@ var "t")
(Just $ analyzeFunctionTermWith_finish @@ var "getTC" @@ var "gEnv" @@ var "tparams" @@ var "args" @@ var "bindings" @@ var "doms" @@ var "tapps" @@ var "t") [
_Term_function>>: "f" ~>
cases _Function (var "f")
(Just $ analyzeFunctionTermWith_finish @@ var "getTC" @@ var "gEnv" @@ var "tparams" @@ var "args" @@ var "bindings" @@ var "doms" @@ var "tapps" @@ var "t") [
_Function_lambda>>: "lam" ~>
Logic.ifElse (var "argMode")
("v" <~ Core.lambdaParameter (var "lam") $
"dom" <~ Maybes.maybe (Core.typeVariable (Core.name (string "_"))) identity (Core.lambdaDomain (var "lam")) $
"body" <~ Core.lambdaBody (var "lam") $
"newEnv" <~ (var "setTC" @@ (Schemas.extendTypeContextForLambda @@ (var "getTC" @@ var "gEnv") @@ var "lam") @@ var "gEnv") $
analyzeFunctionTermWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ var "argMode" @@ var "newEnv"
@@ var "tparams"
@@ (Lists.cons (var "v") (var "args"))
@@ var "bindings"
@@ (Lists.cons (var "dom") (var "doms"))
@@ var "tapps"
@@ var "body")
(analyzeFunctionTermWith_finish @@ var "getTC" @@ var "gEnv" @@ var "tparams" @@ var "args" @@ var "bindings" @@ var "doms" @@ var "tapps" @@ var "t")],
_Term_let>>: "lt" ~>
"newBindings" <~ Core.letBindings (var "lt") $
"body" <~ Core.letBody (var "lt") $
"newEnv" <~ (var "setTC" @@ (Schemas.extendTypeContextForLet @@ var "forBinding" @@ (var "getTC" @@ var "gEnv") @@ var "lt") @@ var "gEnv") $
analyzeFunctionTermWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ boolean False @@ var "newEnv"
@@ var "tparams"
@@ var "args"
@@ (Lists.concat2 (var "bindings") (var "newBindings"))
@@ var "doms"
@@ var "tapps"
@@ var "body",
_Term_typeApplication>>: "ta" ~>
"taBody" <~ Core.typeApplicationTermBody (var "ta") $
"typ" <~ Core.typeApplicationTermType (var "ta") $
analyzeFunctionTermWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ var "argMode" @@ var "gEnv"
@@ var "tparams"
@@ var "args"
@@ var "bindings"
@@ var "doms"
@@ (Lists.cons (var "typ") (var "tapps"))
@@ var "taBody",
_Term_typeLambda>>: "tl" ~>
"tvar" <~ Core.typeLambdaParameter (var "tl") $
"tlBody" <~ Core.typeLambdaBody (var "tl") $
"newEnv" <~ (var "setTC" @@ (Schemas.extendTypeContextForTypeLambda @@ (var "getTC" @@ var "gEnv") @@ var "tl") @@ var "gEnv") $
analyzeFunctionTermWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ var "argMode" @@ var "newEnv"
@@ (Lists.cons (var "tvar") (var "tparams"))
@@ var "args"
@@ var "bindings"
@@ var "doms"
@@ var "tapps"
@@ var "tlBody"]
analyzeFunctionTermWith :: TBinding (
(TypeContext -> Binding -> Maybe Term) ->
(env -> TypeContext) ->
(TypeContext -> env -> env) ->
env ->
Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermWith = define "analyzeFunctionTermWith" $
doc "Analyze a function term with configurable binding metadata" $
"forBinding" ~> "getTC" ~> "setTC" ~> "env" ~> "term" ~>
analyzeFunctionTermWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ boolean True @@ var "env"
@@ list ([] :: [TTerm Name])
@@ list ([] :: [TTerm Name])
@@ list ([] :: [TTerm Binding])
@@ list ([] :: [TTerm Type])
@@ list ([] :: [TTerm Type])
@@ var "term"
-- | Finish helper for analyzeFunctionTermNoInferWith: reapply type applications, skip type inference
analyzeFunctionTermNoInferWith_finish :: TBinding (
env -> [Name] -> [Name] -> [Binding] -> [Type] -> [Type] -> Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermNoInferWith_finish = define "analyzeFunctionTermNoInferWith_finish" $
"fEnv" ~> "tparams" ~> "args" ~> "bindings" ~> "doms" ~> "tapps" ~> "body" ~>
"bodyWithTapps" <~ Lists.foldl
("trm" ~> "typ" ~> Core.termTypeApplication (Core.typeApplicationTerm (var "trm") (var "typ")))
(var "body")
(var "tapps") $
Flows.pure $ record _FunctionStructure [
_FunctionStructure_typeParams>>: Lists.reverse (var "tparams"),
_FunctionStructure_params>>: Lists.reverse (var "args"),
_FunctionStructure_bindings>>: var "bindings",
_FunctionStructure_body>>: var "bodyWithTapps",
_FunctionStructure_domains>>: Lists.reverse (var "doms"),
_FunctionStructure_codomain>>: nothing,
_FunctionStructure_environment>>: var "fEnv"]
-- | Gather helper for analyzeFunctionTermNoInferWith: recursively collect function components
analyzeFunctionTermNoInferWith_gather :: TBinding (
(TypeContext -> Binding -> Maybe Term) ->
(env -> TypeContext) ->
(TypeContext -> env -> env) ->
Bool -> env -> [Name] -> [Name] -> [Binding] -> [Type] -> [Type] -> Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermNoInferWith_gather = define "analyzeFunctionTermNoInferWith_gather" $
"forBinding" ~> "getTC" ~> "setTC" ~>
"argMode" ~> "gEnv" ~> "tparams" ~> "args" ~> "bindings" ~> "doms" ~> "tapps" ~> "t" ~>
cases _Term (Rewriting.deannotateTerm @@ var "t")
(Just $ analyzeFunctionTermNoInferWith_finish @@ var "gEnv" @@ var "tparams" @@ var "args" @@ var "bindings" @@ var "doms" @@ var "tapps" @@ var "t") [
_Term_function>>: "f" ~>
cases _Function (var "f")
(Just $ analyzeFunctionTermNoInferWith_finish @@ var "gEnv" @@ var "tparams" @@ var "args" @@ var "bindings" @@ var "doms" @@ var "tapps" @@ var "t") [
_Function_lambda>>: "lam" ~>
Logic.ifElse (var "argMode")
("v" <~ Core.lambdaParameter (var "lam") $
"dom" <~ Maybes.maybe (Core.typeVariable (Core.name (string "_"))) identity (Core.lambdaDomain (var "lam")) $
"body" <~ Core.lambdaBody (var "lam") $
"newEnv" <~ (var "setTC" @@ (Schemas.extendTypeContextForLambda @@ (var "getTC" @@ var "gEnv") @@ var "lam") @@ var "gEnv") $
analyzeFunctionTermNoInferWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ var "argMode" @@ var "newEnv"
@@ var "tparams"
@@ (Lists.cons (var "v") (var "args"))
@@ var "bindings"
@@ (Lists.cons (var "dom") (var "doms"))
@@ var "tapps"
@@ var "body")
(analyzeFunctionTermNoInferWith_finish @@ var "gEnv" @@ var "tparams" @@ var "args" @@ var "bindings" @@ var "doms" @@ var "tapps" @@ var "t")],
_Term_let>>: "lt" ~>
"newBindings" <~ Core.letBindings (var "lt") $
"body" <~ Core.letBody (var "lt") $
"newEnv" <~ (var "setTC" @@ (Schemas.extendTypeContextForLet @@ var "forBinding" @@ (var "getTC" @@ var "gEnv") @@ var "lt") @@ var "gEnv") $
analyzeFunctionTermNoInferWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ boolean False @@ var "newEnv"
@@ var "tparams"
@@ var "args"
@@ (Lists.concat2 (var "bindings") (var "newBindings"))
@@ var "doms"
@@ var "tapps"
@@ var "body",
_Term_typeApplication>>: "ta" ~>
"taBody" <~ Core.typeApplicationTermBody (var "ta") $
"typ" <~ Core.typeApplicationTermType (var "ta") $
analyzeFunctionTermNoInferWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ var "argMode" @@ var "gEnv"
@@ var "tparams"
@@ var "args"
@@ var "bindings"
@@ var "doms"
@@ (Lists.cons (var "typ") (var "tapps"))
@@ var "taBody",
_Term_typeLambda>>: "tl" ~>
"tvar" <~ Core.typeLambdaParameter (var "tl") $
"tlBody" <~ Core.typeLambdaBody (var "tl") $
"newEnv" <~ (var "setTC" @@ (Schemas.extendTypeContextForTypeLambda @@ (var "getTC" @@ var "gEnv") @@ var "tl") @@ var "gEnv") $
analyzeFunctionTermNoInferWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ var "argMode" @@ var "newEnv"
@@ (Lists.cons (var "tvar") (var "tparams"))
@@ var "args"
@@ var "bindings"
@@ var "doms"
@@ var "tapps"
@@ var "tlBody"]
-- | Internal helper: analyze a function term without type inference, with configurable binding metadata.
analyzeFunctionTermNoInferWith :: TBinding (
(TypeContext -> Binding -> Maybe Term) ->
(env -> TypeContext) ->
(TypeContext -> env -> env) ->
env ->
Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermNoInferWith = define "analyzeFunctionTermNoInferWith" $
doc "Analyze a function term without type inference, with configurable binding metadata" $
"forBinding" ~> "getTC" ~> "setTC" ~> "env" ~> "term" ~>
analyzeFunctionTermNoInferWith_gather @@ var "forBinding" @@ var "getTC" @@ var "setTC"
@@ boolean True @@ var "env"
@@ list ([] :: [TTerm Name])
@@ list ([] :: [TTerm Name])
@@ list ([] :: [TTerm Binding])
@@ list ([] :: [TTerm Type])
@@ list ([] :: [TTerm Type])
@@ var "term"
-- | Analyze a function term by recursively peeling off lambdas, type lambdas, lets, and type applications.
-- This is a common pattern across all language coders: we need to understand the structure of a function
-- to properly encode it in the target language.
analyzeFunctionTerm :: TBinding (
(env -> TypeContext) ->
(TypeContext -> env -> env) ->
env ->
Term ->
Flow s (FunctionStructure env))
analyzeFunctionTerm = define "analyzeFunctionTerm" $
doc "Analyze a function term, collecting lambdas, type lambdas, lets, and type applications" $
"getTC" ~> "setTC" ~> "env" ~> "term" ~>
analyzeFunctionTermWith @@ bindingMetadata @@ var "getTC" @@ var "setTC" @@ var "env" @@ var "term"
-- | Like analyzeFunctionTerm, but without recording binding metadata. This is used for inline
-- lambda expressions where let bindings are encoded as walrus operators (which evaluate
-- immediately and share values, so don't need thunking or function call syntax).
analyzeFunctionTermInline :: TBinding (
(env -> TypeContext) ->
(TypeContext -> env -> env) ->
env ->
Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermInline = define "analyzeFunctionTermInline" $
doc "Analyze a function term without recording binding metadata" $
"getTC" ~> "setTC" ~> "env" ~> "term" ~>
analyzeFunctionTermWith @@ (constant (constant nothing)) @@ var "getTC" @@ var "setTC" @@ var "env" @@ var "term"
-- | Analyze a function term without inferring the return type.
-- This is a performance optimization for dynamically-typed target languages (like Python)
-- where the codomain type is not needed and type inference is expensive.
analyzeFunctionTermNoInfer :: TBinding (
(env -> TypeContext) ->
(TypeContext -> env -> env) ->
env ->
Term ->
Flow s (FunctionStructure env))
analyzeFunctionTermNoInfer = define "analyzeFunctionTermNoInfer" $
doc "Analyze a function term without type inference (performance optimization)" $
"getTC" ~> "setTC" ~> "env" ~> "term" ~>
analyzeFunctionTermNoInferWith @@ bindingMetadata @@ var "getTC" @@ var "setTC" @@ var "env" @@ var "term"
--------------------------------------------------------------------------------
-- State management helpers for coders
--------------------------------------------------------------------------------
-- | Update the metadata portion of a coder state.
-- This is useful for tracking language-specific metadata during code generation.
--
-- Parameters:
-- - getMeta: Extract metadata from state
-- - makeCoder: Construct state from Graph and metadata
-- - getGraph: Extract Graph from state
-- - f: Transformation to apply to metadata
updateCoderMetadata :: TBinding (
(state -> metadata) ->
(Graph -> metadata -> state) ->
(state -> Graph) ->
(metadata -> metadata) ->
Flow state ())
updateCoderMetadata = define "updateCoderMetadata" $
doc "Update the metadata portion of a coder state" $
"getMeta" ~> "makeCoder" ~> "getGraph" ~> "f" ~>
"st" <<~ Monads.getState $
Monads.putState @@ (var "makeCoder" @@ (var "getGraph" @@ var "st") @@ (var "f" @@ (var "getMeta" @@ var "st")))
-- | Temporarily update the graph for a computation, then restore it.
-- The metadata remains mutable throughout the flow (any changes are preserved).
--
-- This pattern is useful when you need to:
-- - Try encoding with an extended graph but not commit the extensions
-- - Test something in a modified graph context
-- - Keep metadata changes but discard graph changes
withUpdatedCoderGraph :: TBinding (
(state -> Graph) ->
(state -> metadata) ->
(Graph -> metadata -> state) ->
(Graph -> Graph) ->
Flow state a ->
Flow state a)
withUpdatedCoderGraph = define "withUpdatedCoderGraph" $
doc "Temporarily update the graph for a computation, then restore it" $
"getGraph" ~> "getMeta" ~> "makeCoder" ~> "f" ~> "flow" ~>
"st" <<~ Monads.getState $
exec (Monads.putState @@ (var "makeCoder" @@ (var "f" @@ (var "getGraph" @@ var "st")) @@ (var "getMeta" @@ var "st"))) $
"r" <<~ var "flow" $
"st2" <<~ Monads.getState $
exec (Monads.putState @@ (var "makeCoder" @@ (var "getGraph" @@ var "st") @@ (var "getMeta" @@ var "st2"))) $
Flows.pure (var "r")
-- | Temporarily extend the graph with additional bindings for a computation.
-- The bindings are only visible within the provided flow action.
--
-- This is commonly used when encoding terms that introduce local bindings
-- (like let expressions), where we need those bindings available in the graph
-- for type checking and term resolution.
withGraphBindings :: TBinding (
(state -> Graph) ->
(Graph -> metadata -> state) ->
(state -> metadata) ->
[Binding] ->
Flow state a ->
Flow state a)
withGraphBindings = define "withGraphBindings" $
doc "Temporarily extend the graph with additional bindings for a computation" $
"getGraph" ~> "makeCoder" ~> "getMeta" ~> "bindings" ~> "flow" ~>
withUpdatedCoderGraph @@ var "getGraph" @@ var "getMeta" @@ var "makeCoder"
@@ (Lexical.extendGraphWithBindings @@ var "bindings")
@@ var "flow"
-- | Run a Flow Graph computation within a Flow state computation.
-- This allows you to execute graph-level operations (like type inference,
-- term lookups, etc.) while maintaining a richer coder state with metadata.
--
-- The graph changes from the inner computation are preserved, but run in
-- the context of the coder's graph.
inCoderGraphContext :: TBinding (
(state -> Graph) ->
(state -> metadata) ->
(Graph -> metadata -> state) ->
Flow Graph a ->
Flow state a)
inCoderGraphContext = define "inCoderGraphContext" $
doc "Run a Flow Graph computation within a Flow state computation" $
"getGraph" ~> "getMeta" ~> "makeCoder" ~> "graphFlow" ~>
"st" <<~ Monads.getState $
"result" <<~ Monads.withState @@ (var "getGraph" @@ var "st") @@ (
"ret" <<~ var "graphFlow" $
"g2" <<~ Monads.getState $
Flows.pure (pair (var "ret") (var "g2"))) $
exec (Monads.putState @@ (var "makeCoder" @@ (Pairs.second (var "result")) @@ (var "getMeta" @@ var "st"))) $
Flows.pure (Pairs.first (var "result"))