Kanren
Port of microKanren to JavaScript.
Install / Use
/learn @joshcox/KanrenREADME
kanren
Introduction
Kanren is a port of microKanren to TypeScript. I'll defer to miniKanren.org for an explanation of what types of problems the kanren family of languages solve.
The initial goal with this particular port is to provide an API that can easily be extended to include additional term types and constraints.
Getting Started
First things first, install kanren: npm install --save kanren
Import kanren.
// es6
import { kanren } from "kanren";
// commonjs
const { kanren } = require("kanren");
const { unify, callWithFresh, disj, conj, runAll } = kanren();
Real quick aside - kanren operates over Terms. No other values are supported within kanren. Note that symbol Terms are reserved for internal use within kanren; please don't use them.
type Term = boolean | undefined | null | number | string | symbol | Array<any>;
Anyway... Now that we have kanren, use runAll to determine if 1 unifies with 1.
> runAll( unify(1, 1) ).size
1
As you can see, there's one state in which the unify(1, 1) goal succeeds. So yes, 1 unifies with 1.
Does 1 unify with 2?
> runAll( unify(1, 2) ).size
0
It sure doesn't. There are 0 states in which the unify(1, 2) goal succeeds. What happens when we unify other Terms?
> runAll( unify([1], 1) ).size
0
> runAll( unify([1], [1]) ).size
1
> runAll( unify([1], [1,2]) ).size
0
> runAll( unify(true, true) ).size
1
> runAll( unify(true, false) ).size
0
> runAll( unify(false, false) ).size
1
So, in general, Terms with equivalent structures and values tend to unify. What about wildcards? The callWithFresh goal wraps a goals and introduces a variable that can be used within the internal goal.
> // Wrap a goal with a hole (`unify(_,5)`) with a function of one argument.
> // Use the single parameter to fill in the hole
> const goalWithVariable = (joker) => unify(joker, 5);
> // Pass `goalWithVariable` to `callWithFresh` to create a goal that creates a logic variable
> runAll( callWithFresh(goalWithVariable) ).size
1
> runAll( callWithFresh((joker) => unify(joker, 5)) ).get(0).substitution.get(0)
{ left: Symbol(0), right: 5 }
> runAll( callWithFresh((joker) => unify(5, joker)) ).get(0).substitution.get(0)
{ left: Symbol(0), right: 5 }
We've just created what's known as a logic variable. Like normal variables in the javascript world, this can be used to represent values. As you can see within the subsitution above, you can assign a logic variable by unifying it with another term. You can also assign logic variables to values within non-primitive terms, such as Arrays.
> runAll( callWithFresh((a) => unify([a], [5])) ).get(0).substitution.get(0)
{ left: Symbol(0), right: 5 }
You might want to make multiple calls to unify. Using conj (conjunction a.k.a logical "and") we can create an aggregate goal that encompasses two smaller goals. Both goals must succeed for the aggregate to succeed.
> runAll( conj(unify(1,1), unify(1,1)) ).size
1
> runAll( conj(unify(1,1), unify(1,2)) ).size
0
> runAll( conj(unify(1,2), unify(1,2)) ).size
0
> runAll( callWithFresh((a) => conj(unify(a,5), unify([a],[5]))) ).size
1
> runAll( callWithFresh((a) => conj(unify(a,5), unify(a,[]))) ).size
0
Like conj, you could also use disj (disjunction a.k.a logical "or") to create an aggregate goal that encompasses two smaller goals. Unlike conj, only one of disj's sub-goal need succeed for the aggregate to succeed.
> runAll( disj(unify(1,1), unify(1,1)) ).size
2
> runAll( disj(unify(1,1), unify(1,2)) ).size
1
> runAll( disj(unify(1,2), unify(1,2)) ).size
0
> runAll( callWithFresh((a) => disj(unify(a,5), unify(a,6))) ).size
2
> runAll( callWithFresh((a) => disj(unify(a,5), unify([a],[]))) ).size
1
Note that disj has the power to create new states; it creates a branch. conj is used to compound new information to existing states.
Work in Progress
- Benchmarking
- Constraint Extensions - Extend
kanrenwith custom constraints - Term type Extensions - Extend
kanrenwith custom term types- [ ] Hook new terms into
unificationalgorithm via a predicate and a unifier
- [ ] Hook new terms into
- Standard Libraries
- [ ]
Array - [ ]
Object
- [ ]
Contributing
kanren is welcoming contributions. If you have something to say, I'd like to hear it. If you have something to code, I'd like to see it. Read more about how to contribute and how to get set up with development here.
Related Skills
node-connect
354.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
112.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
354.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
354.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。

