function Thunk (value) {
this.value = value;
}
function Lambda (fn) {
this.fn = fn;
}
function App (fn, arg) {
this.fn = fn;
this.arg = arg;
}
function Evaluate (val) {
while (val instanceof Thunk) {
val = val.value();
if (val instanceof App) {
val = (PeelLambda(Evaluate(val.fn)))(val.arg);
}
}
return val;
}
function PeelLambda (lam) {
if (!(lam instanceof Lambda)) {
throw "type error: apply a non-lambda to a value"
}
return lam.fn;
}
function Apply (fn) {
return function (arg) {
return new Thunk(function () {
return new App(fn, arg);
});
};
}
var add = new Lambda(function (x) {
return new Lambda(function (y) {
return new Thunk(function () {
return Evaluate(x) + Evaluate(y);
});
});
});
var sub = new Lambda(function (x) {
return new Lambda(function (y) {
return new Thunk(function () {
return Evaluate(x) - Evaluate(y);
});
});
});
function Cons (car, cdr) {
this.car = car;
this.cdr = cdr;
}
function Nil () {
}
// []
var nil = new Thunk(function () {
return new Nil();
});
// (:)
var cons = new Lambda(function (x) {
return new Lambda(function (xs) {
return new Thunk(function () {
return new Cons(x, xs);
});
});
});
// map _ [] = []
// map f (x:xs) = f x : map f xs
var map = new Lambda(function (f) {
return new Lambda(function (list) {
return new Thunk(function () {
var xxs = Evaluate(list);
if (xxs instanceof Cons) {
var x = xxs.car;
var xs = xxs.cdr;
return Apply(Apply(cons)(Apply(f)(x)))
(Apply(Apply(map)(f))(xs));
} else {
return nil;
}
});
});
});
// take _ [] = []
// take n _ | n <= 0 = []
// take n (x:xs) = x : take (n - 1) xs
var take = new Lambda(function (n) {
return new Lambda(function (list) {
return new Thunk(function () {
var xxs = Evaluate(list);
if (xxs instanceof Cons) {
var nval = Evaluate(n);
if (nval <= 0) {
return nil;
} else {
var x = xxs.car;
var xs = xxs.cdr;
return Apply(Apply(cons)(x))
(Apply(Apply(take)(Apply(Apply(sub)(n))(one)))(xs));
}
} else {
return nil;
}
});
});
});
function Unit () {
}
// ()
var unit = new Thunk(function () {
return new Unit();
});
// print = \x -> log x; return ()
var print = new Lambda(function (x) {
return new Thunk(function () {
console.log(Evaluate(x));
return Apply(return_)(unit);
});
});
// return
var return_ = new Lambda(function (x) {
return new Thunk(function () {
return x;
});
});
// (>>)
var then = new Lambda(function (fn1) {
return new Lambda(function (fn2) {
return new Thunk(function () {
Evaluate(fn1);
Evaluate(fn2);
});
});
});
// mapM_ _ [] = return ()
// mapM_ f (x:xs) = f x >> mapM_ f xs
var mapM_ = new Lambda(function (f) {
return new Lambda(function (list) {
return new Thunk(function () {
var xxs = Evaluate(list);
if (xxs instanceof Cons) {
var x = xxs.car;
var xs = xxs.cdr;
return Apply(Apply(then)(Apply(f)(x)))
(Apply(Apply(mapM_)(f))(xs));
} else {
return Apply(return_)(unit);
}
});
});
});
var zero = new Thunk(function () {
return 0;
});
var one = new Thunk(function () {
return 1;
});
var twenty = new Thunk(function () {
return 20;
});
// inf = 0 : map (+1) inf
var inf = new Thunk(function () {
return Apply(Apply(cons)(zero))
(Apply(Apply(map)(Apply(add)(one)))(inf));
});
// zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
// zipWith _ _ _ = []
var zipWith = new Lambda(function (f) {
return new Lambda(function (listx) {
return new Lambda(function (listy) {
return new Thunk(function () {
var xxs = Evaluate(listx);
if (xxs instanceof Cons) {
var yys = Evaluate(listy);
if (yys instanceof Cons) {
return Apply(Apply(cons)(Apply(Apply(f)(xxs.car))(yys.car)))
(Apply(Apply(Apply(zipWith)(f))(xxs.cdr))(yys.cdr));
}
} else {
return nil;
}
});
});
});
});
// tail [] = error "tail: empty list"
// tail (_:xs) = xs
var tail = new Lambda(function (list) {
return new Thunk(function () {
var xxs = Evaluate(list);
if (xxs instanceof Cons) {
return xxs.cdr;
} else {
throw "tail: empty list";
}
});
});
// fib = 0 : 1 : zipWith (+) fib (tail fib)
var fib = new Thunk(function () {
return Apply(Apply(cons)(zero))
(Apply(Apply(cons)(one))
(Apply(Apply(Apply(zipWith)(add))(fib))(Apply(tail)(fib))));
});
Evaluate(Apply(Apply(mapM_)(print))(Apply(Apply(take)(twenty))(fib)));