Core API
Variables
Section titled “Variables”mkVar :: string -> varConstructs a logic variable with the given id. Variables are attrsets { __var = true; id = "..."; }.
ukanren.mkVar "x"# => { __var = true; id = "x"; }isVar :: any -> boolReturns true if the value is a logic variable.
ukanren.isVar (ukanren.mkVar "x") # => trueukanren.isVar 42 # => falsewalk :: subst -> term -> termFollows variable bindings in subst until reaching an unbound variable or a non-variable term.
let subst = { v0 = 42; };in ukanren.walk subst (ukanren.mkVar "v0")# => 42Unification
Section titled “Unification”unify :: term -> term -> subst -> subst | nullUnifies u and v under subst. Returns an extended substitution, or null if unification fails. Recurses into lists (by index) and attrsets (by key — keys must match exactly).
ukanren.unify 1 1 {} # => {}ukanren.unify 1 2 {} # => nullukanren.unify (ukanren.mkVar "v0") 42 {}# => { v0 = 42; }ukanren.unify [ 1 (ukanren.mkVar "v0") ] [ 1 2 ] {}# => { v0 = 2; }A goal is a function state -> stream. States carry { subst; counter; } plus optional extensions (weight, constraints). Streams are lazy linked lists { head; tail; } | null.
succeed
Section titled “succeed”succeed :: goalAlways succeeds. Returns the current state unchanged.
fail :: goalAlways fails. Returns null.
eq :: term -> term -> goalUnifies two terms. Succeeds with an extended substitution if unification succeeds; fails otherwise.
ukanren.run 1 (q: ukanren.eq q 42)# => [ 42 ]fresh :: (var -> goal) -> goalAllocates a fresh variable (named v{counter}), increments the counter, and passes the variable to f.
ukanren.run 1 (q: ukanren.fresh (x: ukanren.eq q [ x 2 ]))# => [ [ { __var = true; id = "v1"; } 2 ] ]The query variable is always v0 (allocated first by run).
conj :: goal -> goal -> goalSequential conjunction. Runs g1, then pipes each resulting state through g2.
ukanren.run 1 (q: ukanren.conj (ukanren.eq q 1) (ukanren.eq q 1) # consistent — succeeds)# => [ 1 ]disj :: goal -> goal -> goalInterleaved disjunction. Runs both goals and merges their streams via mplus (fair interleaving).
ukanren.run 4 (q: ukanren.disj (ukanren.eq q "a") (ukanren.eq q "b"))# => [ "a" "b" ]Reification and running
Section titled “Reification and running”reify :: subst -> term -> termWalks a term deeply, replacing bound variables with their values. Unbound variables are left as-is.
ukanren.reify { v0 = 5; } [ (ukanren.mkVar "v0") 2 ]# => [ 5 2 ]run :: int -> (var -> goal) -> [term]Creates an initial state, allocates the query variable v0 via fresh, runs the goal, takes up to n answers from the stream, and reifies each answer.
ukanren.run 3 (q: ukanren.disj (ukanren.eq q 1) (ukanren.eq q 2))# => [ 1 2 ]Pass 999 (or a large number) to collect all answers from finite relations.
streamTake
Section titled “streamTake”streamTake :: int -> stream -> [state]Pulls up to n states from a lazy stream. Returns raw states (not reified). Used internally by run; available for custom runners.
let stream = ukanren.eq 1 1 { subst = {}; counter = 0; };in ukanren.streamTake 1 stream# => [ { subst = {}; counter = 0; } ]