How to Use `allOfType` to Find All Inequivalent Implementations with Minimal Information Loss in CurryHoward
Call allOfType[T] to generate every distinct minimal-information-loss implementation of type T, filtering out suboptimal variants while preserving all structurally unique solutions.
The curryhoward library for Scala automates proof search to synthesize code from types. When you need to explore every valid implementation of a type without drowning in redundant or low-quality variants, allOfType provides the precise API to retrieve all inequivalent solutions that achieve the minimal information-loss score.
Understanding the allOfType API
How allOfType Differs from ofType and anyOfType
The curryhoward library exposes three distinct entry points for code synthesis, each serving a different exploration need:
| API | Returns | Guarantees |
|---|---|---|
ofType[T] |
A single implementation (the first minimal-loss term) | May throw if multiple minimal solutions exist, reporting ambiguity. |
anyOfType[T] |
All proof terms, including higher-loss variants | Useful for exhaustive search, but can return a huge set containing redundant or "noisy" implementations. |
allOfType[T] |
All inequivalent minimal-loss implementations | Filters out any term whose informationLossScore exceeds the minimum, ensuring a compact yet complete set of optimal solutions. |
Use allOfType when you want the complete landscape of "best" implementations without manual combinator gymnastics.
The Information-Loss Scoring Mechanism
When the theorem prover discovers multiple proof terms for a type, it evaluates each term using informationLossScore. This metric, defined in the TermExpr implementation, measures structural quality by counting:
- Unused arguments in lambda abstractions
- Amount of eta-reduction required to normalize the term
- Redundant match clauses or unused tuple components
Only terms sharing the lowest score survive the filter performed in TheoremProver.findProofs. Consequently, allOfType never returns two implementations that differ only by trivial syntactic noise.
How allOfType Works Under the Hood
The implementation spans three core source files:
-
src/main/scala/io/chymyst/ch/package.scala(lines 37-48): Defines theallOfTypemacro that generates a Scala AST. This AST runs the theorem prover, collects proofs, and returns aSeq[T]. -
src/main/scala/io/chymyst/ch/Macros.scala(lines 404-413): ImplementsallOfTypeImplWithValues, which builds the type expression, incorporates supplied values as named arguments (arg1,arg2, …), and delegates toinhabitAllInternal. -
src/main/scala/io/chymyst/ch/TheoremProver.scala(lines 69-77): ContainsfindProofs, which exhaustively searches for proof terms, scores each with the information-loss metric, groups proofs by score, and returns the lowest-scoring group together with the complete proof set.
The macro ultimately wraps each returned term in a lambda-aware class (Function1Lambda, Function2Lambda, etc.), allowing you to invoke the function normally while retaining access to the underlying proof term.
Practical Examples of Using allOfType
Basic Usage Without Supplied Values
To find all minimal-loss implementations of a simple function type, import the library and call allOfType with the desired type parameter:
import io.chymyst.ch._
// Find all minimal-loss implementations of (Int, Int) => (Int, Int)
val implementations: Seq[(Int, Int) => (Int, Int)] = allOfType[(Int, Int) => (Int, Int)]
// Inspect the generated lambda terms:
implementations.foreach { impl =>
println(impl.lambdaTerm.prettyPrint)
}
This returns every distinct function that can be built from primitive combinators while preserving the type structure, such as pair => pair, pair => (pair._2, pair._1), and other projections. All share the same minimal information-loss score, so none are omitted.
Providing Existing Values as Building Blocks
When you want synthesized terms to use specific existing functions or values, pass them as additional arguments. The macro treats these as named arguments (arg1, arg2, …) available within the generated term:
val inc: Int => Int = _ + 1
val double: Int => Int = _ * 2
// Find all minimal implementations of (Int => Int) => (Int => Int)
val fns: Seq[(Int => Int) => (Int => Int)] = allOfType[(Int => Int) => (Int => Int)](inc, double)
// Examine the lambda term of the first implementation:
println(fns.head.lambdaTerm.prettyPrint)
The prover may produce terms such as g => inc compose g or g => double compose g, using the supplied values as primitive building blocks. Only the minimal-loss variants are returned.
Inspecting the Underlying TermExpr
Each generated value is wrapped in a specialized class that extends the corresponding Scala function trait. To access the raw lambda-calculus representation, use the lambdaTerm extension method defined in package.scala:
val term: TermExpr = implementations.head.lambdaTerm
println(term.prettyPrint) // Human-readable lambda calculus form
The TermExpr reveals exactly how the prover constructed the function—showing applications, curried abstractions, and variable bindings—allowing you to verify the synthesized logic or use the term for further metaprogramming.
When allOfType Returns Empty Results
If the type cannot be inhabited given the available values, the theorem prover returns an empty sequence. For example, attempting to synthesize Int => String without providing any String values or constructors yields no implementations:
// This returns an empty Seq because no String values are in scope
val empty: Seq[Int => String] = allOfType[Int => String]()
This behavior signals a genuine impossibility under the Curry-Howard correspondence—the type is uninhabited with the given assumptions—rather than a library failure.
Summary
allOfType[T]generates every distinct implementation of typeTthat achieves the minimal information-loss score, filtering out redundant or suboptimal variants.- The macro operates by invoking
TheoremProver.findProofs, which groups proof terms by theirinformationLossScoreand returns only the lowest-scoring group. - Supply existing values as arguments to
allOfTypeto incorporate them as named building blocks (arg1,arg2, etc.) in the synthesized terms. - Access the underlying
TermExprvia the.lambdaTermextension to inspect the lambda-calculus representation of each implementation. - An empty result indicates the type is uninhabited given the available values, following the Curry-Howard correspondence.
Frequently Asked Questions
What is the difference between allOfType and anyOfType in curryhoward?
anyOfType[T] returns every proof term the theorem prover discovers, including those with high information-loss scores that represent redundant or "noisy" implementations. In contrast, allOfType[T] filters this set to return only those terms that achieve the minimal information-loss score, giving you all structurally distinct optimal solutions without the clutter of suboptimal variants.
How does curryhoward calculate information-loss scores?
The library assigns an informationLossScore to each TermExpr based on structural complexity metrics. The scoring counts unused arguments in lambda abstractions, the amount of eta-reduction required to normalize the term, and redundant pattern-match clauses or unused tuple components. Lower scores indicate implementations that preserve more of the input structure, and allOfType exclusively returns terms sharing the lowest score found.
Can I use allOfType with generic or higher-kinded types?
Yes, allOfType works with any type the Scala compiler can reify, including generic function types and types involving type constructors, provided the theorem prover can construct a proof term. However, the library operates on the Curry-Howard correspondence, so it can only synthesize total, pure functions. Types requiring side effects, runtime reflection, or external dependencies cannot be inhabited by the prover.
Why does allOfType return an empty sequence for some types?
An empty result indicates that the type is uninhabited given the values and functions available in scope. Under the Curry-Howard correspondence, this means there exists no proof term (lambda expression) that can construct a value of the requested type from the provided assumptions. For example, allOfType[Int => String]() returns an empty sequence unless you provide a way to produce String values from Int inputs.
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 →