How to Use the `WithLambdaTerm` Extension to Access Generated Lambda-Terms at Runtime
The WithLambdaTerm extension adds a lambdaTerm method to any value generated by the ofType or allOfType macros, enabling runtime inspection of the symbolic lambda-calculus representation underlying your synthesized functions.
The curryhoward library leverages the Curry-Howard correspondence to automatically synthesize Scala functions from type signatures. When you invoke the ofType or allOfType macros, the library constructs not only executable bytecode but also a hidden symbolic representation—a lambda-term—that describes the logical structure of the generated code. The WithLambdaTerm extension provides the bridge to access these representations at runtime.
How the WithLambdaTerm Extension Works
The Implicit Class in package.scala
The extension is implemented as an implicit class defined in src/main/scala/io/chymyst/ch/package.scala (lines 81-88). This class enriches any value of type Any with a lambdaTerm method:
implicit class WithLambdaTerm[A](val value: A) {
def lambdaTerm: TermExpr = TermExpr.lambdaTerm(value) match {
case Some(term) => term
case None => throw new Exception(s"Value $value does not have a lambda-term")
}
}
When you call .lambdaTerm on a value, it delegates to TermExpr.lambdaTerm, which pattern-matches against the internal wrapper classes.
Function Wrappers and Term Storage
The macros generate functions wrapped in specialized classes defined in src/main/scala/io/chymyst/ch/TermExpr.scala (lines 889-905). These wrappers—Function0Lambda, Function1Lambda, Function2Lambda, and Function3Lambda—each hold a field lambdaTerm: TermExpr that stores the symbolic representation:
case class Function1Lambda[T1, R](
lambdaTerm: TermExpr,
function: T1 => R
) extends (T1 => R) {
def apply(x: T1): R = function(x)
}
The TermExpr.lambdaTerm helper (lines 75-83) extracts this field:
def lambdaTerm(f: Any): Option[TermExpr] = f match {
case f0: Function0Lambda[_] => Some(f0.lambdaTerm)
case f1: Function1Lambda[_, _] => Some(f1.lambdaTerm)
case f2: Function2Lambda[_, _, _] => Some(f2.lambdaTerm)
case f3: Function3Lambda[_, _, _, _] => Some(f3.lambdaTerm)
case _ => None
}
Accessing Lambda-Terms at Runtime
Basic Usage with ofType
When you generate a function using ofType, the result is automatically wrapped. You can access the lambda-term immediately:
import io.chymyst.ch._
// Generate a function and access its symbolic representation
val increment: Int => Int = ofType[Int => Int](_ + 1)
val term: TermExpr = increment.lambdaTerm
println(term.prettyPrint)
// Output: (λx. (+ x 1))
Working with allOfType
The allOfType macro generates multiple functions simultaneously. Each returned value supports the WithLambdaTerm extension:
val (f1, f2, f3) = allOfType[
(Int => Int),
(Boolean => Boolean),
(String => String)
](
(i: Int) => i + 1,
(b: Boolean) => !b,
(s: String) => s.reverse
)
// Access individual lambda-terms
val term1 = f1.lambdaTerm // λx. (+ x 1)
val term2 = f2.lambdaTerm // λb. (not b)
val term3 = f3.lambdaTerm // λs. (reverse s)
Safe Extraction with TermExpr.lambdaTerm
For values that might not be generated by curryhoward macros, use the safe extraction method that returns an Option:
val maybeTerm = TermExpr.lambdaTerm(someValue)
maybeTerm match {
case Some(term) => println(term.prettyPrint)
case None => println("Value is not a curryhoward-generated function")
}
Comparing and Inspecting Terms
Once extracted, lambda-terms support structural comparison and pretty-printing. Use TermExpr.equiv to check α-equivalence (equality up to variable renaming):
val fA = ofType[Int => Int](_ + 1)
val fB = ofType[Int => Int](_ + 1)
val termA = fA.lambdaTerm
val termB = fB.lambdaTerm
println(TermExpr.equiv(termA, termB)) // true
The prettyPrint method renders terms in standard lambda-calculus notation, making it easy to verify that the synthesized code matches your logical intent.
Summary
- The
WithLambdaTermextension insrc/main/scala/io/chymyst/ch/package.scalaadds alambdaTermmethod to any value, enabling runtime access to symbolic representations. - Generated functions are wrapped in
FunctionNLambdaclasses (defined insrc/main/scala/io/chymyst/ch/TermExpr.scala) that store theTermExprlambda-term. - Use
.lambdaTermdirectly on values created byofTypeorallOfType, or useTermExpr.lambdaTerm(value)for safeOption-based extraction. - Extracted terms support
prettyPrintfor visualization andTermExpr.equivfor α-equivalence comparison.
Frequently Asked Questions
What happens if I call .lambdaTerm on a regular Scala function not generated by curryhoward?
Calling .lambdaTerm on a value that is not wrapped in a FunctionNLambda class will throw an exception stating that the value does not have a lambda-term. To safely check for the presence of a term, use TermExpr.lambdaTerm(value), which returns None for non-generated functions.
Can I access lambda-terms for functions with more than three arguments?
The current implementation in curryhoward provides FunctionNLambda wrappers for arities 0 through 3. Functions with higher arities are curried automatically by the library, so a function of type (A, B, C, D) => R becomes A => B => C => D => R, allowing you to access the lambda-term through the resulting Function1Lambda chain.
How do I compare two generated functions for logical equivalence?
Use TermExpr.equiv(termA, termB) to check for α-equivalence, which determines if two lambda-terms are identical up to variable renaming. This is useful for verifying that two different synthesis paths produced logically equivalent functions, or for testing that your generated code matches an expected symbolic structure.
Is there a performance cost to accessing lambda-terms at runtime?
The lambda-term is stored as a simple field access within the FunctionNLambda wrapper classes, so retrieving it via .lambdaTerm involves only a pattern match and field read—effectively O(1) with negligible overhead. The terms are constructed once during macro expansion at compile time, not at runtime.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →