Skip to content

Commit 32458d4

Browse files
authored
Merge pull request #39 from JohanWiltink/main
call by need
2 parents e792803 + 01cc684 commit 32458d4

File tree

5 files changed

+278
-106
lines changed

5 files changed

+278
-106
lines changed

src/lambda-calculus.js

Lines changed: 61 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,23 @@ class A {
6464
}
6565
}
6666

67-
// can be extended with call by need functionality
68-
class Env extends Map {}
67+
class Env extends Map {
68+
// use inherited set, get for copying references
69+
// insert and retrieve values with setThunk and getValue
70+
// encoding of value is a Generator Object that yields value forever - this is opaque
71+
setThunk(i,thunk) {
72+
this.set(i, function*() {
73+
// console.warn(`expensively calculating ${ i }`);
74+
const result = thunk();
75+
while ( true ) yield result;
76+
} () );
77+
return this;
78+
}
79+
getValue(i) {
80+
// console.warn(`inexpensively fetching ${ i }`);
81+
return this.get(i).next().value;
82+
}
83+
}
6984

7085
// Term and Env pair, used internally to keep track of current computation in eval
7186
class Tuple {
@@ -74,16 +89,11 @@ class Tuple {
7489
toString() { return this.term.toString(); }
7590
}
7691

77-
// Used to insert an external (JS) value into evaluation manually (avoiding implicit number conversion)
78-
function Primitive(v) { return new Tuple(new V( v.name || "<primitive>" ), new Env([[ v.name || "<primitive>" , v ]])); }
92+
// Used to insert an external (JS) value into evaluation manually ( avoiding implicit number conversion )
93+
function Primitive(v) { return new Tuple(new V( "<primitive>" ), new Env([[ "<primitive>" , function*() { while ( true ) yield v; } () ]])); }
7994

80-
const primitives = {
81-
trace: function(v) { console.log(String(v.term)); return v; }
82-
}
83-
84-
for ( const p in primitives ) {
85-
primitives[p] = Primitive(primitives[p]);
86-
}
95+
const primitives = new Env;
96+
primitives.setThunk( "trace", () => evalLC(new Tuple( Primitive( function(v) { console.log(String(v.term)); return v; } ), new Env )) );
8797

8898
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"))))));
8999

@@ -128,9 +138,9 @@ function toIntWith(cfg={}) {
128138
const {numEncoding,verbosity} = Object.assign( {}, config, cfg );
129139
return function toInt(term) {
130140
try {
131-
if ( numEncoding === "Church" )
132-
return term ( x => x+1 ) ( Primitive(0) );
133-
else if ( numEncoding === "Scott" ) {
141+
if ( numEncoding === "Church" ) {
142+
return term ( x => x+1 ) ( Primitive(0) ); // still stack-limited
143+
} else if ( numEncoding === "Scott" ) {
134144
let result = 0, evaluating = true;
135145
while ( evaluating )
136146
term ( () => evaluating = false ) ( n => () => { term = n; result++ } ) ();
@@ -140,19 +150,18 @@ function toIntWith(cfg={}) {
140150
while ( evaluating )
141151
term ( () => evaluating = false ) ( n => () => { term = n; bit *= 2 } ) ( n => () => { term = n; result += bit; bit *= 2 } ) ();
142152
return result;
143-
} else if (numEncoding === "None") {
153+
} else if ( numEncoding === "None" )
144154
return term;
145-
} else {
155+
else
146156
return numEncoding.toInt(term); // Custom encoding
147-
}
148157
} catch (e) {
149158
if ( verbosity >= "Concise" ) console.error(`toInt: ${ term } is not a number in numEncoding ${ numEncoding }`);
150159
throw e;
151160
}
152161
} ;
153162
}
154163

155-
// parse :: String -> {String: Term}
164+
// parse :: String -> Env { String => Term }
156165
function parse(code) { return parseWith()(code); }
157166

158167
function parseWith(cfg={}) {
@@ -164,32 +173,32 @@ function parseWith(cfg={}) {
164173
const FV = term.free(); FV.delete("()");
165174
if ( purity === "Let" )
166175
return Array.from(FV).reduce( (tm,nm) => {
167-
if ( nm in env )
168-
return new A( new L(nm,tm), env[nm] );
169-
else {
176+
if ( env.has(nm) ) {
177+
tm.env.set( nm, env.get(nm) );
178+
return tm;
179+
} else {
170180
if ( verbosity >= "Concise" ) console.error(`parse: while defining ${ name } = ${ term }`);
171181
throw new ReferenceError(`undefined free variable ${ nm }`);
172182
}
173-
} , term );
183+
} , new Tuple( term, new Env ) );
174184
else if ( purity==="LetRec" )
175-
return Array.from(FV).reduce( (tm,nm) => { // this wraps terms in a snapshot of their environment at the moment of defining // TODO: tidy it
185+
return Array.from(FV).reduce( (tm,nm) => {
176186
if ( nm === name )
177187
return tm;
178-
else if ( nm in env )
179-
return new A( new L(nm,tm), env[nm] );
180-
else {
188+
else if ( env.has(nm) ) {
189+
tm.env.set( nm, env.get(nm) );
190+
return tm;
191+
} else {
181192
if ( verbosity >= "Concise" ) console.error(`parse: while defining ${ name } = ${ term }`);
182193
throw new ReferenceError(`undefined free variable ${ nm }`);
183194
}
184-
}
185-
, FV.has(name) ? new A(Y,new L(name,term)) : term
186-
);
195+
} , new Tuple( FV.has(name) ? new A(Y,new L(name,term)) : term , new Env ) );
187196
else if ( purity==="PureLC" )
188197
if ( FV.size ) {
189198
if ( verbosity >= "Concise" ) console.error(`parse: while defining ${ name } = ${ term }`);
190199
throw new EvalError(`unresolvable free variable(s) ${ Array.from(FV) }: all expressions must be closed in PureLC mode`);
191200
} else
192-
return term;
201+
return new Tuple( term, new Env );
193202
else
194203
throw new RangeError(`config.purity: unknown setting "${ purity }"`);
195204
}
@@ -289,15 +298,16 @@ function parseWith(cfg={}) {
289298
const [i,r] = defn(0);
290299
if ( i===code.length ) {
291300
const [name,term] = r;
292-
return Object.assign( env, { [name]: wrap(name,term) } );
301+
const wrapped = wrap(name,term);
302+
return env.setThunk( name, () => evalLC(wrapped) );
293303
} else
294304
error(i,"defn: incomplete parse");
295305
}
296-
return code.replace( /#.*$/gm, "" ) // Ignore comments
297-
.replace( /\n(?=\s)/g, "" )
298-
.split( '\n' )
299-
.filter( term => /\S/.test(term) )
300-
.reduce(parseTerm, Object.assign({}, primitives));
306+
return code.replace( /#.*$/gm, "" ) // ignore comments
307+
.replace( /\n(?=\s)/g, "" ) // continue lines
308+
.split( '\n' ) // split lines
309+
.filter( term => /\S/.test(term) ) // skip empty lines
310+
.reduce(parseTerm, new Env(primitives)); // parse lines
301311
}
302312
}
303313

@@ -307,27 +317,10 @@ function compileWith(cfg={}) {
307317
const {numEncoding,purity,verbosity} = Object.assign( {}, config, cfg );
308318
return function compile(code=fs.readFileSync("./solution.txt", "utf8")) {
309319
const env = parseWith({numEncoding,purity,verbosity})(code);
310-
for ( const [name,term] of Object.entries(env) )
311-
Object.defineProperty( env, name, {
312-
get() {
313-
return env._cache.has(name)
314-
? env._cache.get( name )
315-
: env._cache.set( name, evalLC(term) ).get(name);
316-
}
317-
});
318-
env._cache = new Map; // this needs tearing down when Env gets smart
319-
const envHandler = {
320-
get: function (target, property) {
321-
// Custom undefined error when trying to access functions not defined in environment
322-
const result = Reflect.get(target, property);
323-
if (result === undefined) {
324-
throw ReferenceError(`${ property } is not defined.`);
325-
} else {
326-
return result;
327-
}
328-
}
329-
};
330-
return new Proxy(env, envHandler);
320+
const r = {};
321+
for ( const [name] of env )
322+
Object.defineProperty( r, name, { get() { return env.getValue(name); } } );
323+
return r;
331324
} ;
332325
}
333326

@@ -338,25 +331,23 @@ function evalLC(term) {
338331
function awaitArg(term, stack, env) {
339332

340333
// callback function which will apply the input to the term
341-
const result = function (arg) {
334+
function result(arg) {
342335
let argEnv;
343336
if ( arg.term && arg.env ) ({ term: arg, env: argEnv } = arg); // If callback is passed another callback, or a term
344337
const termVal = new Tuple( typeof arg !== 'number' ? arg : fromInt(arg) , new Env(argEnv) );
345-
const newEnv = new Env(env).set(term.name, termVal);
338+
const newEnv = new Env(env).setThunk(term.name, () => evalLC(termVal));
346339
return runEval(new Tuple(term.body, newEnv), stack);
347-
} ;
348-
349-
// object 'methods/attributes'
340+
}
350341
return Object.assign( result, {term,env} );
351342
}
352343

353-
function runEval({term,env},stack) { // stack: [[term, isRight]], arg: Tuple, env = {name: term}
354-
while ( ! (term instanceof L) || stack.length > 0 ) {
344+
function runEval({term,env},stack) { // stack: [[term, isRight]], term: LC term, env = Env { name => term }
345+
while ( ! ( term instanceof L ) || stack.length > 0 ) {
355346
if ( term instanceof V )
356347
if ( term.name==="()" )
357-
{ console.error(`eval: evaluating undefined inside definition of "${term.defName}"`); throw new EvalError; }
348+
{ console.error(`eval: evaluating undefined inside definition of "${term.defName}"`); throw new EvalError; } // depend on verbosity here
358349
else {
359-
let res = env.get(term.name);
350+
let res = env.getValue(term.name);
360351
if ( ! res.env )
361352
term = res;
362353
else
@@ -368,11 +359,10 @@ function evalLC(term) {
368359
} else if ( term instanceof L ) {
369360
let [ { term: lastTerm, env: lastEnv }, isRight ] = stack.pop();
370361
if ( isRight ) {
371-
if ( term.name !== "_" ) {
372-
env = new Env(env).set(term.name, new Tuple(lastTerm, lastEnv));
373-
}
362+
if ( term.name !== "_" )
363+
env = new Env(env).setThunk(term.name, () => evalLC(new Tuple(lastTerm, lastEnv)));
374364
term = term.body;
375-
} else { // Pass the function some other function. This might need redoing
365+
} else { // Pass the function some other function. This might need redoing // either redo or not. if it works, don't fix it.
376366
term = lastTerm(awaitArg(term, stack, env));
377367
}
378368
} else if ( term instanceof Tuple ) {
@@ -398,7 +388,7 @@ function evalLC(term) {
398388
// We need input
399389
return awaitArg(term, stack, env);
400390
}
401-
return runEval(new Tuple(term, new Env), []);
391+
return runEval(term, []);
402392
}
403393

404394
Object.defineProperty( Function.prototype, "valueOf", { value: function valueOf() { return toInt(this); } } );

tests/basics-church/test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ describe("Church tests",function(){
3434
assert.strictEqual( toInt(three), 3 );
3535
assert.strictEqual( toInt(four), 4 );
3636
assert.strictEqual( toInt(five), 5 );
37-
const n = 1e6;
37+
const n = 1e3;
3838
assert.strictEqual( toInt(I(fromInt(n))), n );
39-
assert.strictEqual( toInt(times(fromInt(1e3))(fromInt(1e3))), 1e6 );
40-
assert.strictEqual( toInt(pow(fromInt(10))(fromInt(6))), 1e6 );
41-
assert.strictEqual( toInt(pred(pow(fromInt(10))(fromInt(4)))), 1e4-1 );
39+
assert.strictEqual( toInt(times(fromInt(1e2))(fromInt(1e1))), 1e3 );
40+
assert.strictEqual( toInt(pow(fromInt(10))(fromInt(3))), 1e3 );
41+
assert.strictEqual( toInt(pred(pow(fromInt(10))(fromInt(3)))), 1e3-1 );
4242
});
4343
});
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# JohanWiltink
2+
3+
# primes
4+
# according to The Real Sieve of Erathosthenes
5+
6+
# B :: (b -> c) -> (a -> b) -> (a -> c)
7+
B = \ f g x . f (g x)
8+
9+
# data Int :: Zero | Succ Int
10+
11+
# succ :: Int -> Int
12+
succ = \ n . \ zero succ . succ n
13+
14+
# plus :: Int -> Int -> Int
15+
plus = \ m n . n m (B succ (plus m))
16+
17+
# mult :: Int -> Int -> Int
18+
mult = \ m n . n n (B (plus m) (mult m))
19+
20+
# minus :: Int -> Int -> Int
21+
minus = \ m n . m m (B (n m) minus)
22+
23+
# data Bool :: False | True
24+
25+
# false :: Bool
26+
false = \ false true . false
27+
28+
# true :: Bool
29+
true = \ false true . true
30+
31+
# isZero :: Int -> Bool
32+
isZero = \ n . n true ( \ n-1 . false )
33+
34+
# EQ :: Int -> Int -> Bool
35+
EQ = \ m n . m (isZero n) (B (n false) EQ)
36+
37+
# data Pair a b :: Pair a b
38+
39+
# pair :: a -> b -> Pair a b
40+
pair = \ x y . \ pair . pair x y
41+
42+
# fst :: Pair a b -> a
43+
fst = \ xy . xy ( \ x y . x )
44+
45+
# snd :: Pair a b -> b
46+
snd = \ xy . xy ( \ x y . y )
47+
48+
# data Stream a :: Cons a (Stream a)
49+
50+
# cons :: a -> Stream a -> Stream a
51+
cons = \ x xs . \ cons . cons x xs
52+
53+
# head :: Stream a -> a
54+
head = \ xs . xs ( \ x xs . x )
55+
56+
# tail :: Stream a -> Stream a
57+
tail = \ xs . xs ( \ x xs . xs )
58+
59+
# map :: (a -> b) -> Stream a -> Stream b
60+
map = \ fn xs . xs ( \ x xs . cons (fn x) (map fn xs) )
61+
62+
# iterate :: (a -> a) -> a -> Stream a
63+
iterate = \ fn x . cons x (iterate fn (fn x))
64+
65+
# LE :: Int -> Int -> Bool
66+
# LE = \ m n . isZero (minus m n)
67+
# LE = \ m n . m true (B (n false) LE) # probably equal performance
68+
69+
# LE :: Stream Int -> Stream Int -> Bool
70+
LE = \ m n . isZero (minus (head m) (head n)) # no need to order on subsequent elements
71+
72+
# data Set a = Nil | Branch a (Set a) (Set a)
73+
74+
# empty :: Set a
75+
empty = \ nil branch . nil
76+
77+
# branch :: a -> Set a -> Set a -> Set a
78+
branch = \ x left right . \ nil branch . branch x left right
79+
80+
# insert :: (Ord a) => a -> Set a -> Set a
81+
insert = \ x set .
82+
set
83+
(branch x empty empty)
84+
( \ y left right .
85+
LE x y
86+
(branch y left (insert x right))
87+
(branch y (insert x left) right)
88+
)
89+
90+
# findMin :: (Partial) => Set a -> a
91+
findMin = \ set . set ()
92+
\ x left right .
93+
left x ( \ _ _ _ . findMin left )
94+
95+
# minView :: (Partial) => Set a -> (a,Set a)
96+
minView = \ set . set ()
97+
\ x left right .
98+
left
99+
(pair x right)
100+
( \ _ _ _ . ( \ unpair . unpair \ y left' . pair y (branch x left' right) ) (minView left) )
101+
102+
# insertPrime :: Stream Int -> Set (Stream Int) -> Set (Stream Int)
103+
insertPrime = \ candidates . candidates
104+
\ prime _ .
105+
insert (map (mult prime) candidates)
106+
107+
# adjust :: Int -> Set (Stream Int) -> Set (Stream Int)
108+
adjust = \ x table .
109+
( \ unpair . unpair
110+
\ uncons table' . uncons
111+
\ n ns .
112+
EQ n x
113+
table
114+
(adjust x (insert ns table'))
115+
)
116+
(minView table)
117+
118+
# sieve :: Set (Stream Int) -> Stream Int -> Stream Int
119+
sieve = \ table xxs . xxs
120+
\ x xs .
121+
( \ uncons . uncons
122+
\ n _ .
123+
EQ n x
124+
(cons x (sieve (insertPrime xxs table) xs))
125+
(sieve (adjust x table) xs)
126+
)
127+
(findMin table)
128+
129+
# firstSieve :: Stream Int -> Stream Int
130+
firstSieve = \ xxs . xxs
131+
\ x xs .
132+
cons x (sieve (insertPrime xxs empty) xs)
133+
134+
# primes :: Stream Int
135+
primes = cons 2 (firstSieve (iterate (plus 2) 3))

0 commit comments

Comments
 (0)