# How to Use the `WithLambdaTerm` Extension to Access Generated Lambda-Terms at Runtime

> Discover how to use the WithLambdaTerm extension to access generated lambdaTerms at runtime. Inspect the symbolic lambda-calculus representation of your synthesized functions.

- Repository: [Chymyst/curryhoward](https://github.com/chymyst/curryhoward)
- Tags: how-to-guide
- Published: 2026-02-27

---

**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`](https://github.com/chymyst/curryhoward/blob/main/package.scala)

The extension is implemented as an implicit class defined in **[`src/main/scala/io/chymyst/ch/package.scala`](https://github.com/chymyst/curryhoward/blob/main/src/main/scala/io/chymyst/ch/package.scala)** (lines 81-88). This class enriches any value of type `Any` with a `lambdaTerm` method:

```scala
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`](https://github.com/chymyst/curryhoward/blob/main/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:

```scala
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:

```scala
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:

```scala
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:

```scala
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`:

```scala
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):

```scala
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 `WithLambdaTerm` extension in **[`src/main/scala/io/chymyst/ch/package.scala`](https://github.com/chymyst/curryhoward/blob/main/src/main/scala/io/chymyst/ch/package.scala)** adds a `lambdaTerm` method to any value, enabling runtime access to symbolic representations.
- Generated functions are wrapped in `FunctionNLambda` classes (defined in **[`src/main/scala/io/chymyst/ch/TermExpr.scala`](https://github.com/chymyst/curryhoward/blob/main/src/main/scala/io/chymyst/ch/TermExpr.scala)**) that store the `TermExpr` lambda-term.
- Use `.lambdaTerm` directly on values created by `ofType` or `allOfType`, or use `TermExpr.lambdaTerm(value)` for safe `Option`-based extraction.
- Extracted terms support `prettyPrint` for visualization and `TermExpr.equiv` for α-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.