JHou stands for "JVM Higher-order unification". The library provides algorithm for higher-order unification.
The implementation based on this paper.
Add following dependency to your project
<dependency>
<groupId>pl.wojciechkarpiel</groupId>
<artifactId>jhou</artifactId>
<version>0.10</version>
</dependency>
For other build tools (SBT, Gradle, Leiningen) see the configuration snippets here
See the API definition.
See this unit test for an API usage example with walk-through comments. This is a quickstart disguised as a unit test.
import pl.wojciechkarpiel.jhou.ast.Term;
import pl.wojciechkarpiel.jhou.ast.Variable;
import pl.wojciechkarpiel.jhou.ast.type.Type;
import pl.wojciechkarpiel.jhou.substitution.Substitution;
import pl.wojciechkarpiel.jhou.unifier.SolutionIterator;
import static pl.wojciechkarpiel.jhou.Api.*;
public class Main {
/**
* Example of unifying `λx.y (C (y x))` and `λx.C x`.
* The result is `y → λx.x`
*/
public static void main(String[] args) {
Type type = freshType(); // we work with typed lambda calculus, so we need some type
Term c = freshConstant(arrow(type, type), "C");
Variable y = freshVariable(arrow(type, type), "y");
Term left = abstraction(x -> app(y, app(c, app(y, x))));
Term right = abstraction(x -> app(c, x));
// result is an iterator over possible substitutions that unify the two sides
SolutionIterator result = unify(left, right);
Substitution solution = result.next();
System.out.println(solution);
// prints: Substitution{[{y → λV_7.V_7}]}
}
}
The pl.wojciechkarpiel.jhou.Api
class contains static methods
described below.
The library deals with higher-order unification in a simply typed lambda calculus, therefore one needs to create types for the term. If your unification problem doesn't care about types, then create a single base type and arrow types based on it.
Type freshType()
- create a new base typeType freshType(String name)
- create a new base type and assign it a name. The name is only for pretty-printing, two fresh types of the same name are distinct.Type arrow(Type from, Type to)
- create an arrow type (i.e. a function type).Type typeOfCurriedFunction(Type arg1, Type arg2, Type... argN)
- helper for nested arrow types, equivalent toarrow(arg1, arrow(arg2, arrow(arg3, ...)))
Example:
Type nat = freshType("ℕ");
Type natToNat = arrow(nat, nat);
Term.equals
is raw structural equality. For other notions of equality, see "Normalization and utility" section.
Variable freshVariable(Type type)
- create a new free variable of typetype
. AVariable
is a subtype ofTerm
Substitution for such variables will be searched for by the unification procedureVariable freshVariable()
- like above, but with type automatically inferred during unificationVariable freshVariable(Type type, String name)
- Name is only for pretty-printing, two fresh variables of the same name are distinct.Variable freshVariable(String name)
- like above, but with type automatically inferred during unificationTerm freshConstant(Type type)
Term freshConstant()
- like above, but with type automatically inferred during unificationTerm freshConstant(Type type, String name)
- Name is only for pretty-printing, two fresh constants of the same name are distinct.Term freshConstant(String name)
- like above, but with type automatically inferred during unificationTerm application(Term function, Term argument)
Term app(Term function, Term argument)
- same asapplication
Term abstraction(Type variableType, Function<Variable, Term> abstraction)
Term abstraction(Function<Variable, Term> abstraction)
- like above, but with type automatically inferred during unificationTerm abstraction(Type variableType, String variableName, Function<Variable, Term> abstraction)
- Name is only for pretty-printingTerm abstraction(String variableName, Function<Variable, Term> abstraction)
- like above, but with type automatically inferred during unification
Term betaNormalize(Term term)
Term etaContract(Term term)
- Simplifies every occurrence of λx.fx into f. Note that this is different from the eta-transformation in the beta-eta normal formbetaEtaNormalize
Term etaExpand(Term term)
Term betaEtaNormalForm(Term term)
- term in beta-eta normal form (see paper)Term betaNormalEtaContracted(Term term)
- equivalent toetaContract(betaNormalize(term)
Type typeOf(Term term)
boolean alphaEqual(Term a, Term b)
boolean alphaBetaEtaEqual(Term a, Term b)
Note: unification for higher-order logics is undecidable, hence the algorithm with no time bound might run forever without producing any result.
SolutionIterator unify(Term a, Term b)
- try to unify termsa
andb
with default UnificationSettings.SolutionIterator unify(Term a, Term b, UnificationSettings unificationSettings)
- see UnificationSettings description
UnificationSettings
is an aggregation of 3 settings:
int maxIterations
(default: unlimited) - Add a computation bound on the unification. The bound is loosely defined as the max height of the search tree. For example, unifyingλx.y (C (y x))
andλx.C x
needs a bound of 2 to find a solution, and bound of 3 to exhaust the search space.PrintStream printStream
(default:System.out
) - use a differentPrintStream
instead ofSystem.out
- this can be used to suppress logging.AllowedTypeInference allowedTypeInference
(default:PERMISSIVE
) - controls what happens in case type inference is needed:NO_INFERENCE_ALLOWED
- throw an exception in case types aren't fully specified in advanceNO_ARBITRARY_SOLUTIONS
- allow type inference only if it generates a unique solution, otherwise throw an exception. For example, the type ofλx.x
ist → t
for an arbitraryt
, hence it would not be allowedPERMISSIVE
- allow type inference, and in case the solution is non-unique, arbitrarily fill in the missing types. For example, inferred type forλx.x
ist → t
, for an arbitrary typet
.
SolutionIterator
returned by the unify()
methods is an iterator over Substitution
.
It's methods are:
boolean hasNext()
- inherited fromIterator
Substitution next()
- inherited fromIterator
Optional<Substitution> peek()
- returns next element from the iterator, but does not consume it. ReturnsOptional.empty()
iffhasNext()
is false.boolean searchLimitReached()
- whenhasNext()
returnsfalse
, one can check if no more solutions are found because of search limit reached (seeUnificationSettings
), or if there are truly no more solutions.
Substitution
is a solution for the unification problem. It's methods are:
Term substitute(Term)
- return a term with free variables substituted. If used on a free variable, then returns the substitution for that variable. Leaves unknown free variables unchanged.
Variable variable = freshVariable(type)
SolutionIterator solutionIterator = unify(leftSide(variable), rightSide(variable));
if (solutionIterator.hasNext()) {
Substitution s = solutionIterator.next();
Term result = s.substitute(variable);
}
TODO: let there be a number expression and find a solution for f x = a * a. Transform between ASTs
- η-conversion is admitted (i.e. function extensionality assumed), see notes in chapter 4 of aforementioned paper
The implementation goal is to make the library widely usable:
- JHou targets bytecode v8
- JHou has no external dependencies
- describe how to use
- provide usage examples
- refactor
- stop disappointing my parents and find a gf