Skip to content

Repository files navigation

exec-symbols

Tests

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.


Table of Contents


Features

  • 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.)

Installation

If you plan to use it in a Node.js project:

npm install exec-symbols

Then, 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')

Quick Start

  1. Create a FactType (relationship type) with a specified arity (the number of noun arguments).
  2. Use makeVerbFact to build a curried function that expects that many nouns.
  3. Represent facts using FactSymbol (if you just need a symbolic representation).
  4. Model constraints as needed, and evaluate them against a collection of facts (the "population").
  5. Use Event and StateMachine to process a stream of events that update your system state.

Core Concepts

Church Booleans

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.

Church Numerals

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 n applies a function f exactly n times to a value
  • The library includes arithmetic operations (ADD, MULT, EXP) and comparisons (EQ, LT, GT, LE, GE)

Church Lists and Pairs

// 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 f and applies f(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).
  • ISEMPTY checks if a list is empty.

Nouns and Binding

constNoun=id=>s=>s(id)constunit=id=>Noun(id)constbind=e=>f=>e(id=>f(id))constget_id=e=>e(id=>id)
  • An Noun is also a function (the same Church-style approach).
  • unit creates an noun from an identifier.
  • bind gives a way to compose noun transformations (similar to a monad).

Relationships and Facts

Relationship Types

constFactType=arity=>verbFn=>reading=>constraints=>s=>s(arity)(verbFn)(reading)(constraints)
  • A FactType captures:
    1. Arity (number of nouns in the relationship),
    2. A verb function,
    3. A reading (a textual representation or something similar),
    4. 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 FactType and returns a curried function that expects exactly arity number of nouns. Once all nouns are provided, it executes the underlying verb function.

Symbolic Facts

constFactSymbol=verb=>nouns=>s=>s(verb)(nouns)
  • A quick way to represent a fact as (verb, [nouns]) in a Church-encoded closure.

Readings

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 Reading represents how to textually represent a fact
  • verb is the verb symbol
  • order is the order of nouns in the reading
  • template is an array of strings that are concatenated with noun IDs

Events

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 Event pairs a fact with a time and optional readings, again using a function-based approach.

State Machines

// 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.
  • StateMachine encapsulates a transition function and an initial state.
  • run_machine processes a stream of events (Church-encoded list) against the transition function.

Constraints and Violations

constConstraint=modality=>predicate=>s=>s(modality)(predicate)constViolation=constraint=>noun=>reason=>s=>s(constraint)(noun)(reason)
  • Constraints contain:

    • Modality (e.g., ALETHIC or DEONTIC).
    • A predicate function to evaluate over a "population."
  • A Violation is a record of which noun violated which constraint, and why.

Examples

Simple Boolean Usage

constisTrue=IF(TRUE)('yes')('no')// 'yes'constisFalse=IF(FALSE)('yes')('no')// 'no'

Executing a Fact

// 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"

Building a Relationship and a Fact

// 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'

Basic State Machine Example

// 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"

Using Readings

// 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)))

Lightweight Symbolic Forum Model Example

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)

License

This library is provided as-is for learning, experimentation, and reference. Feel free to adapt it for your own purposes. MIT License

Testing

This project uses Vitest for testing. To run the tests:

pnpm test

Or with Bun directly:

bun test

About

A fully-functional and extensible metamodeled graph data system with state machines, events, and atomic facts as executable functions. Inspired by ORM and lambda calculus.

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages