├── .gitignore ├── .travis.yml ├── README.md ├── lambda.js ├── lambda.min.js ├── package.json └── test ├── helper.js ├── index.html └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "iojs" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build status](https://secure.travis-ci.org/dankogai/js-lambda.png)](http://travis-ci.org/dankogai/js-lambda) 2 | 3 | js-lambda 4 | ========= 5 | 6 | DSL for, but not limited to, the lambda calculus. 7 | 8 | USAGE 9 | ----- 10 | 11 | ### In Browser 12 | 13 | ````html 14 | 15 | ```` 16 | 17 | ### node.js 18 | 19 | ````javascript 20 | var lambda = require('./lambda.js').lambda, 21 | λ = lambda; 22 | ```` 23 | 24 | SYNOPSIS 25 | -------- 26 | 27 | ````javascript 28 | lambda("x:x")(42) === 42; 29 | λ("x:x")(42) === 42; // λ = lambda 30 | λ("n:n<=1?n:n*_0(n-1)")(10) === 3628800; // _0 for recursion 31 | λ("x,y:Math.sqrt(x*x+y*y)")(3,4) === 5; // multiple arguments, 32 | λ("x:λ(y:Math.sqrt(x*x+y*y))")(3)(4) === 5; // λ can be nested 33 | ```` 34 | ````javascript 35 | // church numerals 36 | var cn2num = λ("f:f(λ(n:n+1))(0)"), 37 | succ = λ("n:λ(f:λ(x:f(n(f)(x))))"), 38 | zero = λ("f:λ(x:x)"), 39 | one = succ(zero), 40 | add = λ("m:λ(n:m("+succ+")(n))"), 41 | two = add(one)(one), 42 | mul = λ("m:λ(n:m("+add+"(n))("+zero+"))"), 43 | four = mul(two)(two), 44 | pow = λ("b:λ(e:e(b))"), 45 | sixteen = pow(two)(four); 46 | cn2num(sixteen) === 16; 47 | ```` 48 | 49 | DESCRIPTION 50 | ----------- 51 | 52 | This script exports `lambda()` and its alias `λ()`. As seen in the synopsis, it is a DSL compiler that returns a function. 53 | 54 | ### the lambda notation 55 | 56 | As seen in [SYNOPSYS](#synopsis), 57 | 58 | `lambda(`*arg0,arg1,...argn*:*expression*`)` 59 | 60 | Turns into: 61 | 62 | `function(`*arg0, arg1, ...argn*`){return ` *expression* `}` 63 | 64 | ### nesting and recursion 65 | 66 | As seen in [SYNOPSYS](#synopsis), the lambda can be nested. 67 | 68 | ````javascript 69 | λ('x:λ(y:Math.sqrt(x*x+y*y))'); 70 | ```` 71 | 72 | Turns into: 73 | 74 | ````javascript 75 | function _0(x){return function _1(y){return Math.sqrt(x*x+y*y)}} 76 | ```` 77 | 78 | As seen above, the function is named accordingly to [De Bruijin index]. `_`*n* is the nth level function. 79 | 80 | [De Bruijin index]: http://en.wikipedia.org/wiki/De_Bruijn_index 81 | 82 | Use the name to implement self-recursion. The strict mode has deprived us of beloved `arguments.callee` but with lambda.js, it is as short as `_0`. 83 | 84 | ````javascript 85 | // function fact(n){ return n <= 1 ? n : n * fact(n-1) } 86 | λ("n:n<=1?n:n*_0(n-1)"); 87 | ```` 88 | 89 | ### limitation 90 | 91 | To use lexical functions, you have to "interpolate". 92 | 93 | ````javascript 94 | var succ = λ("n:λ(f:λ(x:f(n(f)(x))))"), 95 | add = λ("m:λ(n:m("+succ+")(n))"); // λ("m:λ(n:m(succ)(n))") does not work 96 | ```` 97 | 98 | This is because `lambda()` needs to `eval()` to compile the function but lexicals are out of its scope. 99 | 100 | ### memoization 101 | 102 | By default, the compiled function is [memoized]. Suppose you have: 103 | 104 | [memoized]: http://en.wikipedia.org/wiki/Memoization 105 | 106 | ````javascript 107 | function rms(ary) { 108 | return Math.sqrt(ary.reduce(λ("t,x:t+x*x"), 0)) 109 | } 110 | ```` 111 | 112 | And you use `rms` over and over, the same function is used throughout the session. If that is not what you want, you can suppress it by passing truish value to the second argument: 113 | 114 | ````javascript 115 | var uncached = lambda("a,b,c:...", true); 116 | ```` 117 | 118 | And if you wish, you can inspect cached functions via `lambda.memo`. 119 | 120 | 121 | SEE ALSO 122 | -------- 123 | 124 | + http://docs.python.org/2/tutorial/controlflow.html#lambda-forms 125 | + http://www.ruby-doc.org/core-2.0/Kernel.html#method-i-lambda 126 | + http://wiki.ecmascript.org/doku.php?id=harmony:arrow_function_syntax 127 | -------------------------------------------------------------------------------- /lambda.js: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id: lambda.js,v 0.2 2013/04/04 16:19:55 dankogai Exp dankogai $ 3 | * 4 | * lambda.js 5 | * 6 | * (c) 2013 Dan Kogai 7 | * 8 | * Licensed under the MIT license. 9 | * http://www.opensource.org/licenses/mit-license 10 | * 11 | */ 12 | (function(ctx) { 13 | 'use strict'; 14 | var parse = function(src, lv) { 15 | var body = '', idx, m, i, d, l; 16 | while (m = src.match(/^([\s\S]*?)(lambda|λ)\(/)) { 17 | body += m[1]; 18 | src = src.substr(m[0].length-1); 19 | for (i = d = 1, l = src.length; i < l; ++i) { 20 | if (src.charAt(i) === '(') d++; 21 | else if (src.charAt(i) === ')') d--; 22 | if (!d) break; 23 | }; 24 | if (i === l) throw new SyntaxError('() mismatch'); 25 | var t = src.substr(1,i-1); 26 | body += lambda(t, true, lv+1); 27 | src = src.substr(i+1); 28 | } 29 | return body + src; 30 | }; 31 | function lambda(src, nomemo, lv) { 32 | if (!nomemo && src in lambda.memo) return lambda.memo[src]; 33 | if (!lv) lv = 0; 34 | var parts = src.match(/^([^:]*):([\s\S]+)/), 35 | head = parts[1], 36 | body = parse(parts[2], lv); 37 | var fun = eval( 38 | '(function _' + (lv) + '('+head+'){return '+body+'})' 39 | ); 40 | if (!nomemo) lambda.memo[src] = fun; 41 | return fun; 42 | }; 43 | lambda.memo = {}; 44 | ctx.λ = ctx.lambda = lambda; 45 | })(this); 46 | -------------------------------------------------------------------------------- /lambda.min.js: -------------------------------------------------------------------------------- 1 | (function(ctx){"use strict";var parse=function(src,lv){var body="",idx,m,i,d,l;while(m=src.match(/^([\s\S]*?)(lambda|λ)\(/)){body+=m[1];src=src.substr(m[0].length-1);for(i=d=1,l=src.length;i 2 | 3 | 4 | Tester 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 24 | 25 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use mocha to test me 3 | * http://visionmedia.github.com/mocha/ 4 | */ 5 | if (this['window'] !== this) { 6 | require('./helper.js'); 7 | // not "var _"; node also aliases _ to global by default 8 | var lambda = require('../lambda.js').lambda, 9 | λ = lambda; 10 | } 11 | 12 | (function(root){ 13 | 'use strict'; 14 | describe('λ', function(){ 15 | it('λ("x:x")(42) === 42;', eq(λ("x:x")(42), 42)); 16 | it('λ("n:n<=1?n:n*_0(n-1)")(10) === 3628800;', 17 | eq(λ("n:n<=1?n:n*_0(n-1)")(10), 3628800)); 18 | it('λ("x,y:Math.sqrt(x*x+y*y)")(3,4) === 5;', 19 | eq(λ("x,y:Math.sqrt(x*x+y*y)")(3,4), 5)); 20 | it('λ("x:λ(y:Math.sqrt(x*x+y*y))")(3)(4) === 5;', 21 | eq(λ("x:λ(y:Math.sqrt(x*x+y*y))")(3)(4), 5)); 22 | }); 23 | describe('Church numerals', function(){ 24 | var cn2num = λ("f:f(λ(n:n+1))(0)"); 25 | var succ = λ("n:λ(f:λ(x:f(n(f)(x))))"), 26 | zero = λ("f:λ(x:x)"), 27 | one = succ(zero); 28 | it('cn2num(one) === 1', eq(cn2num(one), 1)); 29 | var add = λ("m:λ(n:m("+succ+")(n))"), 30 | two = add(one)(one); 31 | it('cn2num(two) === 2', eq(cn2num(two), 2)); 32 | var mul = λ("m:λ(n:m("+add+"(n))("+zero+"))"), 33 | four = mul(two)(two); 34 | it('cn2num(four) === 4', eq(cn2num(four), 4)); 35 | var pow = λ("b:λ(e:e(b))"), 36 | sixteen = pow(two)(four); 37 | it('cn2num(sixteen) === 16', eq(cn2num(sixteen), 16)); 38 | }); 39 | })(this); 40 | --------------------------------------------------------------------------------