A categorical programming language
修订版 | 245f670944ef60b609a95029f37db32924d75ba4 (tree) |
---|---|
时间 | 2022-10-17 02:57:01 |
作者 | Corbin <cds@corb...> |
Commiter | Corbin |
Rewrite the trampoline in Purescript.
Took a little bit of effort, but it seems worthwhile! The generated code
has the desired loop! I'm impressed.
@@ -1,4 +1,4 @@ | ||
1 | -# export NIX_PATH=nixpkgs=/home/simpson/nixpkgs | |
1 | +export NIX_PATH=nixpkgs=/home/simpson/nixpkgs | |
2 | 2 | use nix |
3 | 3 | eval $(keychain --eval --agents ssh) |
4 | 4 |
@@ -0,0 +1,41 @@ | ||
1 | +module Cammy where | |
2 | + | |
3 | +data Unit = Unit | |
4 | +type Lazy = (->) Unit | |
5 | +data Free f t = Pure t | Free (f (Free f t)) | |
6 | + | |
7 | +-- Cribbed Bilby trampoline, modified and extended to give a monad. | |
8 | +-- Improved using https://stackoverflow.com/a/64854259/264985 | |
9 | +-- Further improved by ripping off Control.Monad.Trampoline | |
10 | +type Tramp = Free Lazy | |
11 | + | |
12 | +done :: forall t. t -> Tramp t | |
13 | +done = Pure | |
14 | + | |
15 | +cont :: forall t. (Unit -> Tramp t) -> Tramp t | |
16 | +cont = Free | |
17 | + | |
18 | +fmap :: forall s t. (s -> t) -> Tramp s -> Tramp t | |
19 | +fmap f (Pure x) = Pure (f x) | |
20 | +fmap f (Free thunk) = Free (\u -> fmap f (thunk u)) | |
21 | + | |
22 | +bind :: forall s t. (s -> Tramp t) -> Tramp s -> Tramp t | |
23 | +bind f (Pure x) = f x | |
24 | +bind f (Free thunk) = Free (\u -> bind f (thunk u)) | |
25 | + | |
26 | +join :: forall t. Tramp (Tramp t) -> Tramp t | |
27 | +join = bind id | |
28 | + | |
29 | +runTrampoline :: forall t. Tramp t -> t | |
30 | +runTrampoline (Pure x) = x | |
31 | +runTrampoline (Free thunk) = runTrampoline (thunk Unit) | |
32 | + | |
33 | +lift :: forall s t. (s -> t) -> s -> Tramp t | |
34 | +lift f x = Pure (f x) | |
35 | + | |
36 | +id :: forall t. t -> t | |
37 | +id x = x | |
38 | + | |
39 | +comp :: forall r s t. (r -> Tramp s) -> (s -> Tramp t) -> r -> Tramp t | |
40 | +comp f g x = bind g (f x) | |
41 | + |
@@ -9,6 +9,7 @@ from .constants import CORE_PRIMITIVES, CORE_TEMPLATES | ||
9 | 9 | from .parser import parse |
10 | 10 | |
11 | 11 | MOVELIST = "/nix/store/dhlswr6d9057lvdq7394dgmlncviv7za-movelist/bin/" |
12 | +PURESCRIPT = "/nix/store/sf7hbhk4bbfq9px1y6442pvlmd6z2zn5-purescript-0.14.7/bin/" | |
12 | 13 | |
13 | 14 | app = Flask(__name__) |
14 | 15 |
@@ -78,7 +79,7 @@ def home(): | ||
78 | 79 | </div> |
79 | 80 | <script |
80 | 81 | src="{ url_for("static", filename="honey.js") }" |
81 | - type="text/javascript"></script> | |
82 | + type="module"></script> | |
82 | 83 | <datalist id="dippers"> |
83 | 84 | {dippers} |
84 | 85 | </datalist> |
@@ -1,3 +1,5 @@ | ||
1 | +import * as Cammy from "./Cammy/index.js"; | |
2 | + | |
1 | 3 | function fetchJSON(url, options = {}) { |
2 | 4 | return fetch(url, options).then(resp => resp.json()); |
3 | 5 | } |
@@ -35,51 +37,9 @@ class Extractor { | ||
35 | 37 | } |
36 | 38 | } |
37 | 39 | |
38 | -function rescueNaN(f) { | |
39 | - return x => { | |
40 | - let y = f(x); | |
41 | - return isNaN(y) ? [false, null] : [true, y]; | |
42 | - }; | |
43 | -} | |
44 | - | |
45 | -// Cribbed Bilby trampoline, modified and extended to give a monad. | |
46 | -// Improved using https://stackoverflow.com/a/64854259/264985 | |
47 | -// X -> Tramp X | |
48 | -function done(result) { return ["pure", result]; } | |
49 | -// [1,Tramp X] -> Tramp X | |
50 | -function cont(thunk) { return ["cont", thunk]; } | |
51 | -// Tramp X × [X,Tramp Y] -> Tramp Y | |
52 | -function bind(action, f) { return ["bind", action, f]; } | |
53 | -// function join(action) { return cont(() => bind(action, ma => ma)); } | |
54 | -// function fmap(f) { return action => bind(action, x => done(f(x))); } | |
55 | -// [X,Tramp Y] × [Y,Tramp Z] -> [X,Tramp Z] | |
56 | -function andThen(f, g) { return x => bind(f(x), g); } | |
57 | -// Tramp X -> X | |
58 | -function trampoline(action) { | |
59 | - // Tramp X | |
60 | - let register = action; | |
61 | - // µS. [X,Tramp Y] × S + 1 | |
62 | - let stack = null; | |
63 | - while (true) { | |
64 | - switch (register[0]) { | |
65 | - case "pure": | |
66 | - if (stack === null) { return register[1]; } | |
67 | - register = stack[0](register[1]); | |
68 | - stack = stack[1]; | |
69 | - break; | |
70 | - case "bind": | |
71 | - stack = [register[2], stack]; | |
72 | - register = register[1]; | |
73 | - break; | |
74 | - case "cont": | |
75 | - register = register[1](); | |
76 | - break; | |
77 | - default: | |
78 | - console.log("stack", stack); | |
79 | - throw new Error("unknown trampoline action " + register[0]); | |
80 | - } | |
81 | - } | |
82 | -} | |
40 | +function fixNaN(x) { return isNaN(x) ? [false, null] : [true, x]; } | |
41 | +function rescueNaN(f) { return x => fixNaN(f(x)); } | |
42 | +function rescueNaN2(f) { return ([x, y]) => fixNaN(f(x, y)); } | |
83 | 43 | |
84 | 44 | const prims = { |
85 | 45 | "id": x => x, |
@@ -87,6 +47,7 @@ const prims = { | ||
87 | 47 | "fst": ([x, y]) => x, |
88 | 48 | "snd": ([x, y]) => y, |
89 | 49 | "dup": x => [x, x], |
50 | + "app": ([f, x]) => f(x), | |
90 | 51 | // XXX not the best encoding for sums |
91 | 52 | "left": x => [true, x], |
92 | 53 | "right": x => [false, x], |
@@ -105,11 +66,10 @@ const prims = { | ||
105 | 66 | "f-zero": _ => 0.0, |
106 | 67 | "f-one": _ => 1.0, |
107 | 68 | "f-pi": _ => Math.PI, |
108 | - // XXX need NaN checks! | |
109 | - "f-add": ([x, y]) => x + y, | |
110 | - "f-mul": ([x, y]) => x * y, | |
69 | + "f-add": rescueNaN2((x, y) => x + y), | |
70 | + "f-mul": rescueNaN2((x, y) => x * y), | |
111 | 71 | "f-negate": x => -x, |
112 | - "f-recip": x => 1 / x, | |
72 | + "f-recip": rescueNaN(x => 1 / x), | |
113 | 73 | "f-sign": x => x <= -0.0, |
114 | 74 | "f-floor": rescueNaN(Math.floor), |
115 | 75 | "f-sqrt": rescueNaN(Math.sqrt), |
@@ -124,11 +84,11 @@ function compile(expr) { | ||
124 | 84 | switch (expr[0]) { |
125 | 85 | case "comp": { |
126 | 86 | const [f, g] = l; |
127 | - return andThen(f, g); | |
87 | + return x => Cammy.bind(f(x), g); | |
128 | 88 | } |
129 | 89 | case "pair": { |
130 | 90 | const [f, g] = l; |
131 | - return x => bind(f(x), y => bind(g(x), z => done([y, z]))); | |
91 | + return x => Cammy.bind(f(x), y => Cammy.bind(g(x), z => Cammy.done([y, z]))); | |
132 | 92 | } |
133 | 93 | case "case": { |
134 | 94 | const [f, g] = l; |
@@ -136,20 +96,18 @@ function compile(expr) { | ||
136 | 96 | } |
137 | 97 | case "curry": { |
138 | 98 | const [f] = l; |
139 | - // XXX slightly wrong... | |
140 | - return x => done(y => f([x, y])); | |
99 | + return x => Cammy.done(y => f([x, y])); | |
141 | 100 | } |
142 | 101 | case "uncurry": { |
143 | 102 | const [f] = l; |
144 | - // XXX ...but also wrong here | |
145 | - return ([x, y]) => bind(f(x), g => g(y)); | |
103 | + return ([x, y]) => Cammy.join(Cammy.bind(f(x), g => g(y))); | |
146 | 104 | } |
147 | 105 | case "pr": { |
148 | 106 | const [z, s] = l; |
149 | 107 | return x => { |
150 | 108 | let rv = z(null); |
151 | 109 | for (let i = 0; i < x; i++) { |
152 | - rv = bind(rv, s); | |
110 | + rv = Cammy.bind(rv, s); | |
153 | 111 | } |
154 | 112 | return rv; |
155 | 113 | }; |
@@ -159,9 +117,9 @@ function compile(expr) { | ||
159 | 117 | return xs => { |
160 | 118 | let rv = n(null); |
161 | 119 | for (let i = 0; i < xs.length; i++) { |
162 | - rv = bind(rv, x => c([xs[i], x])); | |
120 | + rv = Cammy.bind(rv, x => c([xs[i], x])); | |
163 | 121 | } |
164 | - return done(rv); | |
122 | + return Cammy.done(rv); | |
165 | 123 | }; |
166 | 124 | } |
167 | 125 | default: |
@@ -171,7 +129,7 @@ function compile(expr) { | ||
171 | 129 | if (prims[expr] === undefined) { |
172 | 130 | throw new Error("unimplemented prim " + expr); |
173 | 131 | } |
174 | - return x => done(prims[expr](x)); | |
132 | + return x => Cammy.done(prims[expr](x)); | |
175 | 133 | } |
176 | 134 | } |
177 | 135 |
@@ -250,7 +208,7 @@ function tileForIndex(extractor, index, title, trail) { | ||
250 | 208 | const canvas = document.createElement("canvas"); |
251 | 209 | canvas.width = 100; canvas.height = 100; |
252 | 210 | div.appendChild(canvas); |
253 | - drawFrame(canvas, ([x, y]) => trampoline(compiled([x, y])), getChannels); | |
211 | + drawFrame(canvas, ([x, y]) => Cammy.runTrampoline(compiled([x, y])), getChannels); | |
254 | 212 | } else { |
255 | 213 | div.innerHTML += "Not sure how to display this image yet."; |
256 | 214 | } |
@@ -265,7 +223,7 @@ function tileForIndex(extractor, index, title, trail) { | ||
265 | 223 | function stepFrame(timestamp) { |
266 | 224 | if (start === undefined) { start = timestamp; } |
267 | 225 | const t = (timestamp - start) * 0.001; |
268 | - drawFrame(canvas, ([x, y]) => trampoline(compiled([[x, y], t])), getChannels); | |
226 | + drawFrame(canvas, ([x, y]) => Cammy.runTrampoline(compiled([[x, y], t])), getChannels); | |
269 | 227 | setTimeout(() => window.requestAnimationFrame(stepFrame), 1000); |
270 | 228 | } |
271 | 229 | window.requestAnimationFrame(stepFrame); |
@@ -277,7 +235,7 @@ function tileForIndex(extractor, index, title, trail) { | ||
277 | 235 | { |
278 | 236 | div.setAttribute("class", "tile sequence"); |
279 | 237 | const result = Array.from({length: 10}, |
280 | - (_, i) => trampoline(compiled(i))); | |
238 | + (_, i) => Cammy.runTrampoline(compiled(i))); | |
281 | 239 | const guts = result.map(renderer).join("</td><td>"); |
282 | 240 | div.innerHTML += `<table> |
283 | 241 | <tr> |
@@ -288,7 +246,7 @@ function tileForIndex(extractor, index, title, trail) { | ||
288 | 246 | break; |
289 | 247 | case JSON.stringify("1"): |
290 | 248 | div.setAttribute("class", "tile element"); |
291 | - div.innerHTML += renderer(trampoline(compiled(null))); | |
249 | + div.innerHTML += renderer(Cammy.runTrampoline(compiled(null))); | |
292 | 250 | break; |
293 | 251 | default: |
294 | 252 | div.innerHTML += "Not sure how to display this yet."; |
@@ -11,14 +11,14 @@ in pkgs.stdenv.mkDerivation { | ||
11 | 11 | # debugging native code |
12 | 12 | gdb checksec |
13 | 13 | # profiling RPython JITs |
14 | - python3Packages.vmprof | |
14 | + # python3Packages.vmprof | |
15 | 15 | # maintaining embed/ |
16 | 16 | ocamlformat |
17 | 17 | # maintaining movelist/ |
18 | 18 | egg2nix |
19 | 19 | # maintaining cammy-rpy/ |
20 | 20 | # python2Packages.pyflakes |
21 | - python3Packages.pyflakes | |
21 | + python39Packages.pyflakes | |
22 | 22 | # using cammy-comb |
23 | 23 | graphviz |
24 | 24 | # using cammy-draw |
@@ -30,7 +30,7 @@ in pkgs.stdenv.mkDerivation { | ||
30 | 30 | # working with JSON |
31 | 31 | jq |
32 | 32 | # honey |
33 | - python3Packages.flask | |
33 | + python39Packages.flask | |
34 | 34 | # benchmarking |
35 | 35 | busybox feedgnuplot linuxPackages.perf |
36 | 36 | # publishing |
@@ -43,9 +43,8 @@ in pkgs.stdenv.mkDerivation { | ||
43 | 43 | sloccount |
44 | 44 | # experimenting with hive management |
45 | 45 | ranger |
46 | - # ??? | |
47 | - openapi-generator-cli yaml2json python3Packages.virtualenv | |
48 | - # !?!? | |
49 | - wabt binaryen | |
46 | + # experimenting with Honey | |
47 | + # openapi-generator-cli yaml2json python3Packages.virtualenv | |
48 | + purescript | |
50 | 49 | ]; |
51 | 50 | } |