A purely functional TypeScript library for modeling facts, nouns, constraints, and state machines in JavaScript. Functional programming techniques are used for the backends at WhatsApp and X and spam filtering on Facebook, and enables parallelizable, deferred-by-default execution.
This library enables knowledge graphs, object bindings, state machines, and inversion of control all as functional closures. It can be used for rule engines, domain-specific language projects, and more.
- Church Booleans (
TRUE,FALSE,AND,OR,NOT) and combinators (IF) - Church Numerals (
ZERO,SUCC,ADD,MULT,EXP,EQ,LT,GT,LE,GE) - Church-encoded Pairs and Lists (
pair,fst,snd,nil,ISEMPTY,cons,fold,map,append) - Nouns with a monadic interface (
Noun,unit,bind,get_id) - Relationship Types (
FactType) supporting arity, verb function, reading, and constraints - Curried Verb Facts to dynamically build relationships by supplying arguments (
makeVerbFact) - Symbolic Facts (
FactSymbol) and accessors (get_verb_symbol,get_nouns) - Readings (
Reading) with templates, verb accessors, and inverse readings - Events for time-based fact processing
- State Machines with transitions, guard functions, and event-driven updates
- Constraints (alethic vs. deontic), with predicates that evaluate over a "population"
- Violations to track when constraints are broken
- DSL for domain meta-facts (e.g., roles, fact types, constraint references, etc.)
If you plan to use it in a Node.js project:
npm install exec-symbolsThen, in your code:
const{IDENTITY,TRUE,FALSE,IF,AND,OR,NOT,ZERO,SUCC,ADD,MULT,EXP,EQ,LT,GT,LE,GE,
pair, fst, snd,
nil,ISEMPTY, cons, map, fold, append,
Noun, unit, bind, get_id,
equals, nth, reorder,
FactType, get_arity, get_verb, get_reading, get_constraints,
makeVerbFact, FactSymbol, get_verb_symbol, get_nouns,
Reading, get_reading_verb, get_reading_order, get_reading_template,
Event, get_fact, get_time, get_event_readings,
unit_state, bind_state,
make_transition, unguarded,
StateMachine, run_machine, run_noun,
Constraint, get_modality, get_predicate,
evaluate_constraint, evaluate_with_modality,
Violation,
nounType, factType, role, reading, inverseReading,
constraint, constraintTarget, violation,ALETHIC,DEONTIC,RMAP,CSDP}=require('exec-symbols')- Create a FactType (relationship type) with a specified arity (the number of noun arguments).
- Use
makeVerbFactto build a curried function that expects that many nouns. - Represent facts using
FactSymbol(if you just need a symbolic representation). - Model constraints as needed, and evaluate them against a collection of facts (the "population").
- Use
EventandStateMachineto process a stream of events that update your system state.
constTRUE=t=>f=>tconstFALSE=t=>f=>fconstIF=b=>t=>e=>b(t)(e)// AND, OR, NOTconstAND=p=>q=>p(q)(FALSE)constOR=p=>q=>p(TRUE)(q)constNOT=p=>p(FALSE)(TRUE)- These are Church-encoded booleans. They are functions that, given two branches, choose one to evaluate.
constZERO=(a)=>(b)=>bconstSUCC=(n)=>(a)=>(b)=>a(n(a)(b))constADD=(m)=>(n)=>(a)=>(b)=>m(SUCC)(n)(a)(b)constMULT=(m)=>(n)=>(a)=>(b)=>m(n(a))(b)constEXP=(m)=>(n)=>(a)=>(b)=>n(m)(a)(b)constEQ=(m)=>(n)=>AND(LE(m)(n))(LE(n)(m))constLT=(m)=>(n)=>NOT(GE(m)(n))constGT=(m)=>(n)=>NOT(LE(m)(n))constLE=(m)=>(n)=>ISZERO(SUB(m)(n))constGE=(m)=>(n)=>ISZERO(SUB(n)(m))- Church numerals represent natural numbers as functions
- A Church numeral
napplies a functionfexactlyntimes to a value - The library includes arithmetic operations (
ADD,MULT,EXP) and comparisons (EQ,LT,GT,LE,GE)
// Pairsconstpair=a=>b=>f=>f(a)(b)constfst=p=>p((a,_)=>a)constsnd=p=>p((_,b)=>b)// Listsconstnil=c=>n=>nconstISEMPTY=(L)=>L((head)=>(tail)=>FALSE)constcons=h=>t=>c=>n=>c(h)(t(c)(n))constfold=f=>acc=>l=>l(f)(acc)constmap=f=>l=> ...
constappend=l1=>l2=> ...- A pair is stored as a function that takes a function
fand appliesf(a)(b). - A list is stored as a function that takes a function for the "cons" case (
c) and a function for the "nil" case (n). ISEMPTYchecks if a list is empty.
constNoun=id=>s=>s(id)constunit=id=>Noun(id)constbind=e=>f=>e(id=>f(id))constget_id=e=>e(id=>id)- An
Nounis also a function (the same Church-style approach). unitcreates an noun from an identifier.bindgives a way to compose noun transformations (similar to a monad).
constFactType=arity=>verbFn=>reading=>constraints=>s=>s(arity)(verbFn)(reading)(constraints)- A
FactTypecaptures:- Arity (number of nouns in the relationship),
- A verb function,
- A reading (a textual representation or something similar),
- Constraints (additional rules).
makeVerbFact:
constmakeVerbFact=FactType=>{constarity=get_arity(FactType)constverb=get_verb(FactType)constcurry=(args,n)=>n===0
? verb(args)
: arg=>curry(append(args)(cons(arg)(nil)),n-1)returncurry(nil,arity)}- Takes a
FactTypeand returns a curried function that expects exactlyaritynumber of nouns. Once all nouns are provided, it executes the underlying verb function.
constFactSymbol=verb=>nouns=>s=>s(verb)(nouns)- A quick way to represent a fact as
(verb, [nouns])in a Church-encoded closure.
constReading=(verb,order,template)=>(s)=>s(verb,order,template)constget_reading_verb=(r)=>r((v,o,t)=>v)constget_reading_order=(r)=>r((v,o,t)=>o)constget_reading_template=(r)=>r((v,o,t)=>t)- A
Readingrepresents how to textually represent a fact verbis the verb symbolorderis the order of nouns in the readingtemplateis an array of strings that are concatenated with noun IDs
constEvent=fact=>time=>readings=>s=>s(fact,time,readings)constget_fact=e=>e((f,t,r)=>f)constget_time=e=>e((f,t,r)=>t)constget_event_readings=e=>e((f,t,r)=>r)- An
Eventpairs a fact with a time and optional readings, again using a function-based approach.
// State Monadconstunit_state=a=>s=>pair(a)(s)constbind_state=m=>f=>s=>{/* typical state-monad logic */}// Transitionconstmake_transition=guard=>compute_next=>state=>input=>IF(guard(state)(input))(compute_next(state)(input))(state)// Unguarded transitionconstunguarded=make_transition((_s)=>(_i)=>TRUE)// StateMachineconstStateMachine=transition=>initial=>s=>s(transition)(initial)// Running a machineconstrun_machine=machine=>stream=>machine((transition,initial)=>fold(event=>state=>transition(state)(get_fact(event)))(initial)(stream))- The code includes guarded transitions (using Church booleans) and a state monad for carrying and updating state.
StateMachineencapsulates a transition function and an initial state.run_machineprocesses a stream of events (Church-encoded list) against the transition function.
constConstraint=modality=>predicate=>s=>s(modality)(predicate)constViolation=constraint=>noun=>reason=>s=>s(constraint)(noun)(reason)Constraints contain:
- Modality (e.g.,
ALETHICorDEONTIC). - A predicate function to evaluate over a "population."
- Modality (e.g.,
A
Violationis a record of which noun violated which constraint, and why.
constisTrue=IF(TRUE)('yes')('no')// 'yes'constisFalse=IF(FALSE)('yes')('no')// 'no'// Define a selector that creates a readable stringconstreadableSelector=(verb,nouns)=>{constnounValues=map(get_id)(nouns);// Get reading for this verb (simplified lookup)constreadingInfo=/* lookup reading for verb */;consttemplate=get_reading_template(readingInfo);constorder=get_reading_order(readingInfo);// Reorder nouns according to reading orderconstorderedNouns=reorder(nounValues,order);// Apply template for any arityreturnfold((value,index)=>(str)=>str.replace(`{${index}}`,value),template,orderedNouns);}// Example usage:constaliceKnowsBob=FactSymbol('knows')(list(unit('Alice'),unit('Bob')));constreadableString=aliceKnowsBob(readableSelector);// readableString would be something like "Alice knows Bob"// Define a relationship type: "loves", arity = 2constlovesFactType=FactType(2)(args=>{// A simple verb function that returns a FactSymbolreturnFactSymbol('loves')(args)})(['',' loves ','']// reading)(nil// no additional constraints)// Make a verb fact for "loves"constloves=makeVerbFact(lovesFactType)// Provide two nounsconstalice=unit("Alice")constbob=unit("Bob")// Curried usageconstfact=loves(alice)(bob)// => FactSymbol('loves')(cons(alice)(cons(bob)(nil)))// Inspectconsole.log(get_verb_symbol(fact))// 'loves'console.log(get_id(nth(0)(get_nouns(fact))))// 'Alice'console.log(get_id(nth(1)(get_nouns(fact))))// 'Bob'// Define a simple guard and next stateconstguard=state=>input=>// For demonstration, only proceed if input matches "go"equals(input)(unit("go"))constcompute_next=state=>input=>// Return a new state, e.g., "running"pair(unit("running"))(snd(state))// Make a transitionconsttransition=make_transition(guard)(compute_next)// Initial machineconstmyMachine=StateMachine(transition)(pair(unit("idle"))(nil))// Stream of eventsconsteventStream=cons(Event(unit("go"))(0)(nil))(cons(Event(unit("stop"))(1)(nil))(nil))// RunconstfinalState=run_machine(myMachine)(eventStream)console.log(get_id(fst(finalState)))// "running" if it processed "go"// Define a readingconstlovesReading=Reading('loves',cons(ZERO)(cons(SUCC(ZERO))(nil)),['',' loves ',''])// Create an inverse reading (B is loved by A instead of A loves B)constlovedByReading=inverseReading('loves','is_loved_by',cons(SUCC(ZERO))(cons(ZERO)(nil)),['',' is loved by ',''])// Use in event with readingsconstevent=Event(loves(alice)(bob))('now')(cons(lovesReading)(cons(lovedByReading)(nil)))Demonstrates:
- Executable verbs
- FactTypes and Readings
- Inverse readings (manually declared)
- Event emission with all readings
- Deontic constraint requiring inverse reading
- Minimal fact population with post/reply/moderation
// ───────────── Nouns ─────────────constalice=unit("alice")constbob=unit("bob")constthread1=unit("thread-1")constpostA=unit("post-A")constpostB=unit("post-B")// ───────────── FactType: posts ─────────────constpostsVerb=args=>{const[user,post,thread]=[nth(0)(args),nth(1)(args),nth(2)(args)]returnFactSymbol("posts")(args)}constpostsType=FactType(3)(postsVerb)([""," posted "," in ",""])(nil)// Reading: forwardreading("posts",[""," posted "," in ",""])// Inverse reading - thread contains postsinverseReading("posts","contains",cons(2)(cons(1)(cons(0)(nil))),[""," contains post "," by ",""])// ───────────── FactType: replies ─────────────constrepliesVerb=args=>{const[user,replyPost,originalPost]=[nth(0)(args),nth(1)(args),nth(2)(args)]returnFactSymbol("replies")(args)}constrepliesType=FactType(3)(repliesVerb)([""," replied with "," to ",""])(nil)reading("replies",[""," replied with "," to ",""])// Inverse reading for replies - has reply frominverseReading("replies","hasReplyFrom",cons(2)(cons(1)(cons(0)(nil))),[""," has reply "," from ",""])// ───────────── FactType: moderates ─────────────constmoderatesVerb=args=>{const[moderator,post]=[nth(0)(args),nth(1)(args)]returnFactSymbol("moderates")(args)}constmoderatesType=FactType(2)(moderatesVerb)([""," moderated ",""])(nil)reading("moderates",[""," moderated ",""])// ───────────── Deontic Constraint: inverse required for posts ─────────────constinverseRequiredForPosts=Constraint(DEONTIC)(pop=>{constfound=any(pop,f=>get_verb_symbol(f)==="inverseReading"&&get_id(nth(0)(get_nouns(f)))==="posts")returnfound ? TRUE : FALSE})constraint("inverse_required_for_posts",DEONTIC)constraintTarget("inverse_required_for_posts","posts",0)// ───────────── Fact Instances ─────────────// Alice posts postA in thread1constpostFact=makeVerbFact(postsType)(alice)(postA)(thread1)// Bob replies to postA with postBconstreplyFact=makeVerbFact(repliesType)(bob)(postB)(postA)// Alice moderates Bob's postconstmodFact=makeVerbFact(moderatesType)(alice)(postB)// ───────────── Events ─────────────// Inverse reading list provided to EventconstinverseReadingsForPosts=cons(Reading("contains",cons(2)(cons(1)(cons(0)(nil))),[""," contains post "," by ",""]))(nil)constevent1=Event(postFact)(unit("t1"))(inverseReadingsForPosts)constevent2=Event(replyFact)(unit("t2"))(nil)constevent3=Event(modFact)(unit("t3"))(nil)// ───────────── Constraint Evaluation ─────────────constpop=cons(inverseReading("posts","contains",cons(2)(cons(1)(cons(0)(nil))),[""," contains post "," by ",""]))(nil)constevalResult=evaluate_with_modality(inverseRequiredForPosts)(pop)// Expected: pair(DEONTIC)(TRUE)This library is provided as-is for learning, experimentation, and reference. Feel free to adapt it for your own purposes. MIT License
This project uses Vitest for testing. To run the tests:
pnpm testOr with Bun directly:
bun test