Skip to content

Commit a50431f

Browse files
committed
Factor out recursion
1 parent 4399715 commit a50431f

File tree

1 file changed

+32
-15
lines changed

1 file changed

+32
-15
lines changed

src/lambda-calculus.js

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,19 @@ class Env extends Map {
6868
// use inherited set, get for copying references
6969
// insert and retrieve values with setThunk and getValue
7070
// encoding of value is a Generator Object that yields value forever - this is opaque
71-
setThunk(i,thunk) {
71+
setThunk(i,val) {
7272
this.set(i, function*() {
7373
// console.warn(`expensively calculating ${ i }`);
74-
const result = thunk();
74+
let result = (yield val) ?? val; // If val is not A or V, then it need not be evaluated
7575
while ( true ) yield result;
7676
} () );
7777
return this;
7878
}
79-
getValue(i) {
79+
// Second argument provides an interface for storing the evaluated term inside
80+
// the generator, for future accesses.
81+
getValue(i, result) {
8082
// console.warn(`inexpensively fetching ${ i }`);
81-
return this.get(i).next().value;
83+
return this.get(i).next(result).value;
8284
}
8385
}
8486

@@ -93,7 +95,7 @@ class Tuple {
9395
function Primitive(v) { return new Tuple(new V( "<primitive>" ), new Env([[ "<primitive>" , function*() { while ( true ) yield v; } () ]])); }
9496

9597
const primitives = new Env;
96-
primitives.setThunk( "trace", () => evalLC(new Tuple( Primitive( function(v) { console.log(String(v.term)); return v; } ), new Env )) );
98+
primitives.setThunk( "trace", new Tuple( Primitive( function(v) { console.log(String(v.term)); return v; } ), new Env ) );
9799

98100
const Y = new L("f",new A(new L("x",new A(new V("f"),new A(new V("x"),new V("x")))),new L("x",new A(new V("f"),new A(new V("x"),new V("x"))))));
99101

@@ -299,7 +301,7 @@ function parseWith(cfg={}) {
299301
if ( i===code.length ) {
300302
const [name,term] = r;
301303
const wrapped = wrap(name,term);
302-
return env.setThunk( name, () => evalLC(wrapped) );
304+
return env.setThunk( name, wrapped);
303305
} else
304306
error(i,"defn: incomplete parse");
305307
}
@@ -319,7 +321,7 @@ function compileWith(cfg={}) {
319321
const env = parseWith({numEncoding,purity,verbosity})(code);
320322
const r = {};
321323
for ( const [name] of env )
322-
Object.defineProperty( r, name, { get() { return env.getValue(name); }, enumerable: true } );
324+
Object.defineProperty( r, name, { get() { return evalLC(new Tuple(new V(name), env)); }, enumerable: true } );
323325
return r;
324326
} ;
325327
}
@@ -335,13 +337,16 @@ function evalLC(term) {
335337
let argEnv;
336338
if ( arg.term && arg.env ) ({ term: arg, env: argEnv } = arg); // If callback is passed another callback, or a term
337339
const termVal = new Tuple( typeof arg !== 'number' ? arg : fromInt(arg) , new Env(argEnv) );
338-
const newEnv = new Env(env).setThunk(term.name, () => evalLC(termVal));
340+
const newEnv = new Env(env).setThunk(term.name, termVal);
339341
return runEval(new Tuple(term.body, newEnv), stack);
340342
}
341343
return Object.assign( result, {term,env} );
342344
}
343345

344-
function runEval({term,env},stack) { // stack: [[term, isRight]], term: LC term, env = Env { name => term }
346+
// term :: Tuple
347+
// isRight :: bool (indicating whether the term is left or right side of an Application)
348+
// isEvaluated :: bool (indicating whether the current term should be stored in the Env)
349+
function runEval({term,env},stack) { // stack: [[term, isRight, isEvaluated]], term: LC term, env = Env { name => term }
345350
while ( ! ( term instanceof L ) || stack.length > 0 ) {
346351
if ( term instanceof V )
347352
if ( term.name==="()" )
@@ -350,17 +355,25 @@ function evalLC(term) {
350355
let res = env.getValue(term.name);
351356
if ( ! res.env )
352357
term = res;
353-
else
358+
else {
359+
if (res.term instanceof V || res.term instanceof A)
360+
// Push a frame to the stack to indicate when the value should be stored back
361+
stack.push( [new Tuple( term, env ), false, true ] );
354362
({term, env} = res);
363+
}
355364
}
356365
else if ( term instanceof A ) {
357366
stack.push([ new Tuple(term.right, new Env(env)), true ]);
358367
term = term.left;
359368
} else if ( term instanceof L ) {
360-
let [ { term: lastTerm, env: lastEnv }, isRight ] = stack.pop();
361-
if ( isRight ) {
369+
let [ { term: lastTerm, env: lastEnv }, isRight, isEvaluated ] = stack.pop();
370+
if ( isEvaluated ) {
371+
// A non-evaluated term was received from an Env, but it is now evaluated.
372+
// Store it.
373+
lastEnv.getValue(lastTerm.name, new Tuple(term, env));
374+
} else if ( isRight ) {
362375
if ( term.name !== "_" )
363-
env = new Env(env).setThunk(term.name, () => evalLC(new Tuple(lastTerm, lastEnv)));
376+
env = new Env(env).setThunk(term.name, new Tuple(lastTerm, lastEnv));
364377
term = term.body;
365378
} else { // Pass the function some other function.
366379
term = lastTerm(awaitArg(term, stack, env));
@@ -370,8 +383,12 @@ function evalLC(term) {
370383
({term, env} = term);
371384
} else { // Not a term
372385
if ( stack.length === 0 ) return term;
373-
let [ { term: lastTerm, env: lastEnv }, isRight ] = stack.pop();
374-
if ( isRight ) {
386+
let [ { term: lastTerm, env: lastEnv }, isRight, isEvaluated ] = stack.pop();
387+
if ( isEvaluated ) {
388+
// A non-evaluated term was received from an Env, but it is now evaluated.
389+
// Store it.
390+
lastEnv.getValue(lastTerm.name, new Tuple(term, env));
391+
} else if ( isRight ) {
375392
stack.push([ new Tuple(term, new Env(env)), false ]);
376393
term = lastTerm;
377394
env = lastEnv;

0 commit comments

Comments
 (0)