|
// exec-symbols.js |
|
|
|
// ───────────── Lambda Calculus Primitives ───────────── |
|
|
|
const TRUE = t => f => t |
|
const FALSE = t => f => f |
|
const IF = b => t => e => b(t)(e) |
|
|
|
const AND = p => q => p(q)(FALSE) |
|
const OR = p => q => p(TRUE)(q) |
|
const NOT = p => p(FALSE)(TRUE) |
|
|
|
const pair = a => b => f => f(a)(b) |
|
const fst = p => p((a, b) => a) |
|
const snd = p => p((a, b) => b) |
|
|
|
const nil = c => n => n |
|
const cons = h => t => c => n => c(h)(t(c)(n)) |
|
const map = f => l => l((h, t) => cons(f(h))(t))(nil) |
|
const fold = f => acc => l => l(f)(acc) |
|
const append = l1 => l2 => fold(cons)(l2)(l1) |
|
|
|
// ───────────── Utilities ───────────── |
|
|
|
const equals = a => b => get_id(a) === get_id(b) |
|
|
|
const nth = n => list => { |
|
let i = 0 |
|
return list((h, t) => { |
|
if (i === n) return h |
|
i++ |
|
return t |
|
})(null) |
|
} |
|
|
|
const mapIndex = list => { |
|
const out = [] |
|
let i = 0 |
|
list((h, t) => { |
|
out.push(unit(i)) |
|
i++ |
|
return t |
|
})(null) |
|
return out.reduceRight((acc, v) => cons(v)(acc), nil) |
|
} |
|
|
|
const reorder = (entities, order) => |
|
map(i => nth(i)(entities))(order) |
|
|
|
// ───────────── Entities ───────────── |
|
|
|
const Entity = id => s => s(id) |
|
const unit = id => Entity(id) |
|
const bind = e => f => e(id => f(id)) |
|
const get_id = e => e(id => id) |
|
|
|
// ───────────── Readings ───────────── |
|
|
|
const make_reading = (verb, order, template) => |
|
s => s(verb)(order)(template) |
|
|
|
const get_reading_verb = r => r((v, o, t) => v) |
|
const get_reading_order = r => r((v, o, t) => o) |
|
const get_reading_template = r => r((v, o, t) => t) |
|
|
|
// ───────────── RelType (no inverseReadings) ───────────── |
|
|
|
const RelType = arity => verbFn => reading => constraints => |
|
s => s(arity)(verbFn)(reading)(constraints) |
|
|
|
const get_arity = rt => rt((a, v, r, c) => a) |
|
const get_verb = rt => rt((a, v, r, c) => v) |
|
const get_reading = rt => rt((a, v, r, c) => r) |
|
const get_constraints = rt => rt((a, v, r, c) => c) |
|
|
|
// ───────────── Executable Facts ───────────── |
|
|
|
const makeVerbFact = relType => { |
|
const arity = get_arity(relType) |
|
const verb = get_verb(relType) |
|
|
|
const curry = (args, n) => |
|
n === 0 |
|
? verb(args) |
|
: arg => curry(append(args)(cons(arg)(nil)), n - 1) |
|
|
|
return curry(nil, arity) |
|
} |
|
|
|
const FactSymbol = verb => entities => s => s(verb)(entities) |
|
const get_verb_symbol = f => f((v, e) => v) |
|
const get_entities = f => f((v, e) => e) |
|
|
|
// ───────────── Events with Readings (look up inverses externally) ───────────── |
|
|
|
const Event = fact => time => readings => s => s(fact)(time)(readings) |
|
const get_fact = e => e((f, t, r) => f) |
|
const get_time = e => e((f, t, r) => t) |
|
const get_event_readings = e => e((f, t, r) => r) |
|
|
|
// Inverse readings must be provided externally (queried by the caller) |
|
const emit_event = (fact, time, inverseReadings = nil) => { |
|
const relType = get_reltype(fact) |
|
const original = get_entities(fact) |
|
|
|
const primary = make_reading( |
|
get_verb_symbol(fact), |
|
mapIndex(original), |
|
get_reading(relType) |
|
) |
|
|
|
return Event(fact)(time)(cons(primary)(inverseReadings)) |
|
} |
|
|
|
// ───────────── State Machine ───────────── |
|
|
|
const unit_state = a => s => pair(a)(s) |
|
|
|
const bind_state = m => f => s => { |
|
const result = m(s) |
|
const a = fst(result) |
|
const s_ = snd(result) |
|
return f(a)(s_) |
|
} |
|
|
|
const make_transition = guard => compute_next => |
|
state => input => |
|
IF(guard(state)(input))( |
|
compute_next(state)(input) |
|
)( |
|
state |
|
) |
|
|
|
const unguarded = make_transition((_s => _i => TRUE)) |
|
|
|
const StateMachine = transition => initial => s => s(transition)(initial) |
|
|
|
const run_machine = machine => stream => |
|
machine((transition, initial) => |
|
fold(event => state => |
|
transition(state)(get_fact(event)) |
|
)(initial)(stream) |
|
) |
|
|
|
const run_entity = machine => stream => |
|
run_machine(machine)(stream) |
|
|
|
// ───────────── Constraints & Violations ───────────── |
|
|
|
const ALETHIC = 'alethic' |
|
const DEONTIC = 'deontic' |
|
|
|
const Constraint = modality => predicate => s => s(modality)(predicate) |
|
const get_modality = c => c((m, _) => m) |
|
const get_predicate = c => c((_, p) => p) |
|
|
|
const evaluate_constraint = constraint => pop => |
|
get_predicate(constraint)(pop) |
|
|
|
const evaluate_with_modality = constraint => pop => { |
|
const result = evaluate_constraint(constraint)(pop) |
|
const modal = get_modality(constraint) |
|
return pair(modal)(result) |
|
} |
|
|
|
const Violation = constraint => entity => reason => s => s(constraint)(entity)(reason) |
|
|
|
// ───────────── Meta-Fact Declarations ───────────── |
|
|
|
const entityType = name => |
|
FactSymbol("entityType")(cons(unit(name))(nil)) |
|
|
|
const factType = (verb, arity) => |
|
FactSymbol("factType")(cons(unit(verb))(cons(unit(arity))(nil))) |
|
|
|
const role = (verb, index, name) => |
|
FactSymbol("role")(cons(unit(verb))(cons(unit(index))(cons(unit(name))(nil)))) |
|
|
|
const reading = (verb, parts) => |
|
FactSymbol("reading")(cons(unit(verb))(cons(parts)(nil))) |
|
|
|
const inverseReading = (primary, inverse, order, template) => |
|
FactSymbol("inverseReading")( |
|
cons(unit(primary))( |
|
cons(unit(inverse))( |
|
cons(order)( |
|
cons(template)(nil)))) |
|
) |
|
|
|
const constraint = (id, modality) => |
|
FactSymbol("constraint")(cons(unit(id))(cons(unit(modality))(nil))) |
|
|
|
const constraintTarget = (constraintId, verb, roleIndex) => |
|
FactSymbol("constraintTarget")(cons(unit(constraintId))(cons(unit(verb))(cons(unit(roleIndex))(nil)))) |
|
|
|
const violation = (entity, constraintId, reason) => |
|
FactSymbol("violation")(cons(unit(entity))(cons(unit(constraintId))(cons(unit(reason))(nil)))) |
|
|
|
// ───────────── Reserved Symbols ───────────── |
|
|
|
const RMAP = Symbol('RMAP') |
|
const CSDP = Symbol('CSDP') |
|
|
|
// ───────────── Exports ───────────── |
|
|
|
module.exports = { |
|
TRUE, FALSE, IF, AND, OR, NOT, |
|
pair, fst, snd, |
|
nil, cons, map, fold, append, |
|
Entity, unit, bind, get_id, |
|
equals, nth, mapIndex, reorder, |
|
RelType, get_arity, get_verb, get_reading, get_constraints, |
|
makeVerbFact, FactSymbol, get_verb_symbol, get_entities, |
|
Reading: make_reading, get_reading_verb, get_reading_order, get_reading_template, |
|
Event, emit_event, get_fact, get_time, get_event_readings, |
|
unit_state, bind_state, make_transition, unguarded, |
|
StateMachine, run_machine, run_entity, |
|
Constraint, get_modality, get_predicate, |
|
evaluate_constraint, evaluate_with_modality, |
|
Violation, |
|
entityType, factType, role, reading, inverseReading, |
|
constraint, constraintTarget, violation, |
|
ALETHIC, DEONTIC, |
|
RMAP, CSDP |
|
} |
|
|
|
/* |
|
* Lightweight Symbolic Forum Model Example |
|
* |
|
* Demonstrates: |
|
* - Executable verbs |
|
* - RelTypes and Readings |
|
* - Inverse readings (manually declared) |
|
* - Event emission with all readings |
|
* - Deontic constraint requiring inverse reading |
|
* - Minimal fact population with post/reply/moderation |
|
* |
|
|
|
// ───────────── Entities ───────────── |
|
const alice = unit("alice") |
|
const bob = unit("bob") |
|
const thread1 = unit("thread-1") |
|
const postA = unit("post-A") |
|
const postB = unit("post-B") |
|
|
|
// ───────────── RelType: posts ───────────── |
|
const postsVerb = args => { |
|
const [user, post, thread] = [nth(0)(args), nth(1)(args), nth(2)(args)] |
|
return FactSymbol("posts")(args) |
|
} |
|
|
|
const postsType = RelType(3)(postsVerb)( |
|
["", " posted ", " in ", ""] |
|
)(nil) |
|
|
|
// Reading: forward |
|
reading("posts", ["", " posted ", " in ", ""]) |
|
|
|
// Inverse reading (manually declared) |
|
inverseReading("posts", "receivesPostFrom", cons(2)(cons(1)(cons(0)(nil))), |
|
["", " received ", " from ", ""]) |
|
|
|
// ───────────── RelType: replies ───────────── |
|
const repliesVerb = args => { |
|
const [user, replyPost, originalPost] = [nth(0)(args), nth(1)(args), nth(2)(args)] |
|
return FactSymbol("replies")(args) |
|
} |
|
|
|
const repliesType = RelType(3)(repliesVerb)( |
|
["", " replied with ", " to ", ""] |
|
)(nil) |
|
|
|
reading("replies", ["", " replied with ", " to ", ""]) |
|
|
|
// ───────────── RelType: moderates ───────────── |
|
const moderatesVerb = args => { |
|
const [moderator, post] = [nth(0)(args), nth(1)(args)] |
|
return FactSymbol("moderates")(args) |
|
} |
|
|
|
const moderatesType = RelType(2)(moderatesVerb)( |
|
["", " moderated ", ""] |
|
)(nil) |
|
|
|
reading("moderates", ["", " moderated ", ""]) |
|
|
|
// ───────────── Deontic Constraint: inverse required for posts ───────────── |
|
const inverseRequiredForPosts = Constraint(DEONTIC)( |
|
pop => { |
|
const found = any(pop, f => |
|
get_verb_symbol(f) === "inverseReading" && |
|
get_id(nth(0)(get_entities(f))) === "posts" |
|
) |
|
return found ? TRUE : FALSE |
|
} |
|
) |
|
|
|
constraint("inverse_required_for_posts", DEONTIC) |
|
constraintTarget("inverse_required_for_posts", "posts", 0) |
|
|
|
// ───────────── Fact Instances ───────────── |
|
// Alice posts postA in thread1 |
|
const postFact = makeVerbFact(postsType)(alice)(postA)(thread1) |
|
|
|
// Bob replies to postA with postB |
|
const replyFact = makeVerbFact(repliesType)(bob)(postB)(postA) |
|
|
|
// Alice moderates Bob's post |
|
const modFact = makeVerbFact(moderatesType)(alice)(postB) |
|
|
|
// ───────────── Events ───────────── |
|
// Inverse reading list manually provided to emit_event |
|
const inverseReadingsForPosts = cons( |
|
make_reading("receivesPostFrom", cons(2)(cons(1)(cons(0)(nil))), |
|
["", " received ", " from ", ""]) |
|
)(nil) |
|
|
|
const event1 = emit_event(postFact, unit("t1"), inverseReadingsForPosts) |
|
const event2 = emit_event(replyFact, unit("t2")) |
|
const event3 = emit_event(modFact, unit("t3")) |
|
|
|
// ───────────── Constraint Evaluation ───────────── |
|
const pop = cons( |
|
inverseReading("posts", "receivesPostFrom", cons(2)(cons(1)(cons(0)(nil))), |
|
["", " received ", " from ", ""]) |
|
)(nil) |
|
|
|
const evalResult = evaluate_with_modality(inverseRequiredForPosts)(pop) |
|
// Expected: pair(DEONTIC)(TRUE) |
|
*/ |