A categorical programming language
修订版 | 83402fcc7add55f224434c3f7938d765977e2415 (tree) |
---|---|
时间 | 2022-09-12 01:49:26 |
作者 | Corbin <cds@corb...> |
Commiter | Corbin |
Check in a bunch of random files that I've had sitting around.
@@ -0,0 +1,5 @@ | ||
1 | +# export NIX_PATH=nixpkgs=/home/simpson/nixpkgs | |
2 | +use nix | |
3 | +eval $(keychain --eval --agents ssh) | |
4 | + | |
5 | +# export CAMMY_HIVE="$(pwd)/hive/" |
@@ -8,6 +8,7 @@ components: | ||
8 | 8 | type: string |
9 | 9 | Token: |
10 | 10 | type: integer |
11 | + format: int64 | |
11 | 12 | Functor: |
12 | 13 | type: array |
13 | 14 | items: |
@@ -0,0 +1,79 @@ | ||
1 | +#!/usr/bin/env nix-shell | |
2 | +#! nix-shell -i python3 -p python3 | |
3 | + | |
4 | +# This is a simple HTTP service written in Python 3. It exposes jelly to the | |
5 | +# network by allowing requests to implicitly call jelly via an opaque | |
6 | +# membrane. The quality of jelly is not guaranteed. | |
7 | + | |
8 | +import hashlib | |
9 | +import http.server | |
10 | +import json | |
11 | +import urllib.parse | |
12 | +import sys | |
13 | +import threading | |
14 | + | |
15 | +# Expressions are stored in cache, rather than durable storage. The current | |
16 | +# storage is a dict protected by a reader-writer lock. | |
17 | + | |
18 | +cache = {} | |
19 | +cacheLock = threading.Lock() | |
20 | + | |
21 | +# Expressions are keyed by unguessable tokens. | |
22 | + | |
23 | +def makeToken(expr): | |
24 | + return hashlib.sha256(expr).hexdigest() | |
25 | +TOKEN_LENGTH = len(makeToken(b"id")) | |
26 | + | |
27 | +class JellyHandler(http.server.BaseHTTPRequestHandler): | |
28 | + | |
29 | + # All responses will be valid JSON. | |
30 | + | |
31 | + def finish_JSON(self, value): | |
32 | + encoded = json.dumps(value).encode("utf-8") | |
33 | + self.send_response(http.server.HTTPStatus.OK) | |
34 | + self.send_header("Content-Type", "application/json") | |
35 | + self.send_header("Content-Length", len(encoded)) | |
36 | + self.end_headers() | |
37 | + self.wfile.write(encoded) | |
38 | + | |
39 | + # When a token is requested, look for an equivalent expression. | |
40 | + | |
41 | + def do_GET(self): | |
42 | + url = urllib.parse.urlparse(self.path) | |
43 | + # Remove the leading slash. | |
44 | + key = url.path[1:] | |
45 | + if len(key) != TOKEN_LENGTH: | |
46 | + self.send_error(http.server.HTTPStatus.NOT_FOUND) | |
47 | + return | |
48 | + | |
49 | + try: | |
50 | + # XXX timing attacks happen here | |
51 | + with cacheLock: | |
52 | + expr = cache[key] | |
53 | + except KeyError: | |
54 | + self.send_error(http.server.HTTPStatus.NOT_FOUND) | |
55 | + return | |
56 | + | |
57 | + self.finish_JSON(expr) | |
58 | + | |
59 | + def do_PUT(self): | |
60 | + exprLength = int(self.headers.get("Content-Length")) | |
61 | + expr = self.rfile.read(exprLength) | |
62 | + decoded = expr.decode("utf-8") | |
63 | + key = makeToken(expr) | |
64 | + with cacheLock: | |
65 | + cache[key] = decoded | |
66 | + | |
67 | + self.finish_JSON(key) | |
68 | + | |
69 | +PORT = 8000 | |
70 | + | |
71 | +def main(): | |
72 | + print("Token length is %d" % TOKEN_LENGTH, file=sys.stderr) | |
73 | + print("Starting HTTP server on port %d" % PORT, file=sys.stderr) | |
74 | + server = http.server.ThreadingHTTPServer(("", PORT), JellyHandler) | |
75 | + server.serve_forever() | |
76 | + return 0 | |
77 | + | |
78 | +if __name__ == "__main__": | |
79 | + sys.exit(main()) |
@@ -0,0 +1,30 @@ | ||
1 | +What's the smallest expression implementing nat/add? | |
2 | + | |
3 | +nat/add : N × N → N | |
4 | + | |
5 | +Must be uncurried from primitive recursion: | |
6 | + | |
7 | +(curry nat/add) = (pr X F) : N → [N, N] | |
8 | +X : 1 → [N, N] | |
9 | +F : [N, N] → [N, N] | |
10 | + | |
11 | +Forall n, 0 + n = n, so: | |
12 | + | |
13 | +(comp (pair zero id) nat/add) = id : N → N | |
14 | + | |
15 | +This means that X must name a function N → N which sends every n to n: | |
16 | + | |
17 | +X = (name id) = (curry snd) : 1 → [N, N] | |
18 | + | |
19 | +F must be uncurried: | |
20 | + | |
21 | +(uncurry F) : [N, N] × N → N | |
22 | + | |
23 | +We need to apply and +1. This is the smallest way to do that: | |
24 | + | |
25 | +(comp app succ) = (comp (uncurry id) succ) : [N, N] × N → N | |
26 | + | |
27 | +So: | |
28 | + | |
29 | +(uncurry F) = (comp app succ) : [N, N] × N → N | |
30 | +F = (curry (comp app succ)) : [N, N] → [N, N] |
@@ -0,0 +1,36 @@ | ||
1 | +#!/usr/bin/env python3 | |
2 | +# nix-shell -i python3 -p python3 | |
3 | + | |
4 | +# A simple parser and translator from SKI combinator calculus to Cammy. | |
5 | +# Well-formedness of input and well-typedness of output are not enforced. | |
6 | + | |
7 | +def parse(expr): | |
8 | + stack = [[]] | |
9 | + for char in expr: | |
10 | + if char == "(": | |
11 | + stack.append([]) | |
12 | + elif char == ")": | |
13 | + row = stack.pop() | |
14 | + stack[-1].append(row) | |
15 | + elif char in "SKIBCW": | |
16 | + stack[-1].append(char) | |
17 | + return stack[-1] | |
18 | + | |
19 | +expr = parse("S(K(S(S K K)(S K K)))(S(S(K S)(S(K K)(S K K)))(K(S(S K K)(S K K))))") | |
20 | + | |
21 | +print(expr) | |
22 | + | |
23 | +templates = { | |
24 | + "K": "(curry fst)", | |
25 | + "S": "(curry (curry (comp (pair (comp (pair (comp fst fst) snd) app) (comp (pair (comp fst snd) snd) app)) app)))", | |
26 | +} | |
27 | + | |
28 | +def translate(tree): | |
29 | + if isinstance(tree, str): | |
30 | + return templates[tree] | |
31 | + else: | |
32 | + # Trust input; don't check arity. | |
33 | + expr = translate(tree[0]) | |
34 | + for t in tree[1:]: | |
35 | + expr = "(comp (pair %s %s) app)" % (expr, translate(t)) | |
36 | + return expr |