-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmagic.js
106 lines (95 loc) · 2.95 KB
/
magic.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Turn Array into a monad.
// Usual definition of flatten; see, e.g.
// http://goo.gl/lLQpes
Object.defineProperty(Array.prototype, "flatten", {
enumerable:false,
writable:true,
configurable:true,
value: function () {
return this.reduce(function (previous, current) {
return previous.concat(current);
});
}
});
// L-system for growing trees; see, e.g.
// https://www.khanacademy.org/computer-programming/tree/1029209629
// twig: Array<number>
function makeGrow(twig) {
// number => Array<number>
return function grow(n) {
if (n === 0) { return twig; }
if (n === 1) { return [1, 1]; }
return [n];
};
}
// Abstract Maybe class
function Maybe() { throw Error("abstract!"); }
Maybe.prototype.map = function (f) {
if (this instanceof Some) {
return some(f(this.v));
} else {
return none;
}
};
Maybe.prototype.flatten = function () {
if (this instanceof Some) {
return this.v;
} else {
return none;
}
};
// Concrete constructors for Maybe
function Some(v) { this.v = v; }
Some.prototype = Object.create(Maybe.prototype);
Some.prototype.toString = function () { return "some(" + JSON.stringify(this.v) + ")"; };
function some(v) { return new Some(v); }
function None() { }
None.prototype = Object.create(Maybe.prototype);
None.prototype.toString = function () { return "none"; };
var none = new None();
// invert:number => Maybe<number>
function invert(n) {
if (n === 0) { return none; }
return some(1/n);
}
// Magical stuff here
var M = (function () {
// Computation stack in case we have nested monadic
// bind expressions
var stack = [];
// Magic 1: when an object is used as an argument to
// greater-than, its valueOf function is invoked. The
// default implementation for function objects isn't
// side-effecting, but we can make it map the function
// over the state and then flatten (i.e. monadic bind
// is map followed by the monadic multiplication).
// This implementation is independent of the map and
// flatten methods of the object on the top of the stack.
Function.prototype.valueOf = function () {
if (stack[0] && stack[0].map) {
stack[0] = stack[0].map(this).flatten();
}
return this;
};
// Magic 2: the result of an expression involving
// greater-than is a boolean. We can add a property to
// booleans that pops the result off the stack.
Object.defineProperty(Boolean.prototype, "_", {
get: function() {
return stack.pop();
}
});
// Monadic unit pushes state onto the top of the stack.
return {
array: function (s) { stack.push([s]); },
maybe: function (s) { stack.push(some(s)); }
};
})();
// Choose a nice twig.
var grow = makeGrow([1,4,2,0,5,1,4,3,0,5,0]);
// Grow it twice.
console.log( (M.array(0) > grow > grow)._ );
// Inverting twice is the identity on non-zero numbers
console.log('' + (M.maybe(5) > invert > invert)._ );
// Inverting twice is undefined on zero
console.log('' + (M.maybe(0) > invert > invert)._ );