A categorical programming language
リビジョン | f89591928cda9053c1baaa0ff6252d2465c40a95 (tree) |
---|---|
日時 | 2022-02-27 13:21:36 |
作者 | Corbin <cds@corb...> |
コミッター | Corbin |
Get the typechecker to emit reasonable output.
@@ -192,7 +192,7 @@ class Case(Arrow): | ||
192 | 192 | fdom, fcod = self.f.types(cs) |
193 | 193 | gdom, gcod = self.g.types(cs) |
194 | 194 | cs.unify(fcod, gcod) |
195 | - return fdom, cs.functor("sum", [fdom, gdom]) | |
195 | + return cs.functor("sum", [fdom, gdom]), fdom | |
196 | 196 | |
197 | 197 | class Curry(Arrow): |
198 | 198 | _immutable_ = True |
@@ -519,16 +519,12 @@ def buildCompound(name, args): | ||
519 | 519 | raise BuildProblem("Invalid compound functor: " + name) |
520 | 520 | |
521 | 521 | def buildArrow(sexp): |
522 | - try: | |
523 | - if isinstance(sexp, Atom): | |
524 | - return buildUnary(sexp.symbol) | |
525 | - elif isinstance(sexp, Functor): | |
526 | - args = [buildArrow(arg) for arg in sexp.arguments] | |
527 | - return buildCompound(sexp.constructor, args) | |
528 | - elif isinstance(sexp, Hole): | |
529 | - raise BuildProblem("Holes cannot become arrows") | |
530 | - else: | |
531 | - assert False, "inconceivable" | |
532 | - except BuildProblem as bp: | |
533 | - print "Couldn't build Cammy expression:", bp.message | |
534 | - raise | |
522 | + if isinstance(sexp, Atom): | |
523 | + return buildUnary(sexp.symbol) | |
524 | + elif isinstance(sexp, Functor): | |
525 | + args = [buildArrow(arg) for arg in sexp.arguments] | |
526 | + return buildCompound(sexp.constructor, args) | |
527 | + elif isinstance(sexp, Hole): | |
528 | + raise BuildProblem("Holes cannot become arrows") | |
529 | + else: | |
530 | + assert False, "inconceivable" |
@@ -18,15 +18,18 @@ class Hive(object): | ||
18 | 18 | self.exprs = {} |
19 | 19 | |
20 | 20 | def load(self, atom): |
21 | + if atom in self.exprs: | |
22 | + return self.exprs[atom] | |
23 | + | |
21 | 24 | # print("Loading", atom) |
22 | 25 | filename = atom + ".cammy" |
23 | 26 | fullpath = os.path.join(self.hivepath, filename) |
24 | - with open(fullpath, "r") as handle: | |
25 | - try: | |
27 | + try: | |
28 | + with open(fullpath, "r") as handle: | |
26 | 29 | sexp, trail = parse(handle.read()) |
27 | 30 | sexp = sexp.canonicalize(self) |
28 | 31 | self.exprs[atom] = sexp |
29 | 32 | # print("Loaded", atom, sexp) |
30 | 33 | return sexp |
31 | - except IOError: | |
32 | - raise MissingAtom(atom) | |
34 | + except IOError: | |
35 | + raise MissingAtom(atom) |
@@ -4,6 +4,29 @@ class UnificationFailed(Exception): | ||
4 | 4 | def __init__(self, message): |
5 | 5 | self.message = message |
6 | 6 | |
7 | +def occurs(index, var): | |
8 | + if isinstance(var, Hole): | |
9 | + return var.index == index | |
10 | + elif isinstance(var, Atom): | |
11 | + return False | |
12 | + elif isinstance(var, Functor): | |
13 | + for arg in var.arguments: | |
14 | + if occurs(index, arg): | |
15 | + return True | |
16 | + return False | |
17 | + else: | |
18 | + assert False, "impossible" | |
19 | + | |
20 | +def occursCheck(index, var): | |
21 | + "If an index occurs in a variable, raise an error." | |
22 | + if occurs(index, var): | |
23 | + raise UnificationFailed("Occurs check: %d in %s" % | |
24 | + (index, var.asStr())) | |
25 | + | |
26 | +def unhole(sexp): | |
27 | + assert isinstance(sexp, Hole), "implementation error" | |
28 | + return sexp.index | |
29 | + | |
7 | 30 | class ConstraintStore(object): |
8 | 31 | "A Kanren-style constraint store." |
9 | 32 |
@@ -30,31 +53,24 @@ class ConstraintStore(object): | ||
30 | 53 | self.constraints.append(Functor(constructor, args)) |
31 | 54 | return rv |
32 | 55 | |
33 | - def walkArg(self, var): | |
34 | - if isinstance(var, Atom): | |
35 | - return var | |
36 | - elif isinstance(var, Functor): | |
37 | - return Functor(var.constructor, | |
38 | - [self.walkArg(arg) for arg in var.arguments]) | |
39 | - elif isinstance(var, Hole): | |
40 | - return self.walk(var.index) | |
41 | - assert False, "impossible" | |
42 | - | |
43 | 56 | def walk(self, i): |
44 | 57 | var = self.constraints[i] |
45 | - if isinstance(var, Hole) and var.index == i: | |
46 | - return var | |
58 | + if isinstance(var, Hole) and var.index != i: | |
59 | + return self.walk(var.index) | |
47 | 60 | else: |
48 | - return self.walkArg(var) | |
61 | + return var | |
49 | 62 | |
50 | 63 | def unify(self, i, j): |
51 | - return self.unifyArg(self.walk(i), self.walk(j)) | |
52 | - | |
53 | - def unifyArg(self, vi, vj): | |
54 | - print "unifying", vi.asStr(), vj.asStr() | |
64 | + vi = self.walk(i) | |
65 | + vj = self.walk(j) | |
66 | + # print "unifying", vi.asStr(), vj.asStr() | |
55 | 67 | if isinstance(vi, Hole): |
68 | + if isinstance(vj, Hole) and vi.index == vj.index: | |
69 | + return | |
70 | + occursCheck(vi.index, vj) | |
56 | 71 | self.constraints[vi.index] = vj |
57 | 72 | elif isinstance(vj, Hole): |
73 | + occursCheck(vj.index, vi) | |
58 | 74 | self.constraints[vj.index] = vi |
59 | 75 | elif isinstance(vi, Atom) and isinstance(vj, Atom): |
60 | 76 | if vi.symbol != vj.symbol: |
@@ -69,29 +85,40 @@ class ConstraintStore(object): | ||
69 | 85 | if len(vi.arguments) != len(vj.arguments): |
70 | 86 | raise UnificationFailed("Compound types have different arity?") |
71 | 87 | for i, argi in enumerate(vi.arguments): |
72 | - self.unifyArg(argi, vj.arguments[i]) | |
88 | + self.unify(unhole(argi), unhole(vj.arguments[i])) | |
73 | 89 | else: |
74 | 90 | raise UnificationFailed("Quite different types: %s. vs %s" % |
75 | 91 | (vi.asStr(), vj.asStr())) |
76 | 92 | |
77 | -def formatType(sexp): | |
78 | - if isinstance(sexp, Hole): | |
79 | - return "_%d" % sexp.index | |
80 | - elif isinstance(sexp, Atom): | |
81 | - return sexp.symbol | |
82 | - elif isinstance(sexp, Functor): | |
83 | - if sexp.constructor == "hom": | |
84 | - return "[%s, %s]" % (formatType(sexp.arguments[0]), | |
85 | - formatType(sexp.arguments[1])) | |
86 | - elif sexp.constructor == "pair": | |
87 | - return "(%s x %s)" % (formatType(sexp.arguments[0]), | |
88 | - formatType(sexp.arguments[1])) | |
89 | - elif sexp.constructor == "sum": | |
90 | - return "(%s + %s)" % (formatType(sexp.arguments[0]), | |
91 | - formatType(sexp.arguments[1])) | |
92 | - elif sexp.constructor == "list": | |
93 | - return "[%s]" % formatType(sexp.arguments[0]) | |
93 | + | |
94 | +LETTERS = "XYZWSTPQ" | |
95 | + | |
96 | +class TypeExtractor(object): | |
97 | + def __init__(self): | |
98 | + self.d = {} | |
99 | + | |
100 | + def addTypeAlias(self, index): | |
101 | + self.d[index] = LETTERS[len(self.d)] | |
102 | + | |
103 | + def extractType(self, cs, var): | |
104 | + sexp = cs.walk(var) | |
105 | + if isinstance(sexp, Hole): | |
106 | + if sexp.index not in self.d: | |
107 | + self.addTypeAlias(sexp.index) | |
108 | + return self.d[sexp.index] | |
109 | + elif isinstance(sexp, Atom): | |
110 | + return sexp.symbol | |
111 | + elif isinstance(sexp, Functor): | |
112 | + args = [self.extractType(cs, unhole(arg)) for arg in sexp.arguments] | |
113 | + if sexp.constructor == "hom": | |
114 | + return "[%s, %s]" % (args[0], args[1]) | |
115 | + elif sexp.constructor == "pair": | |
116 | + return "(%s x %s)" % (args[0], args[1]) | |
117 | + elif sexp.constructor == "sum": | |
118 | + return "(%s + %s)" % (args[0], args[1]) | |
119 | + elif sexp.constructor == "list": | |
120 | + return "[%s]" % args[0] | |
121 | + else: | |
122 | + assert False, "whoops" | |
94 | 123 | else: |
95 | - assert False, "whoops" | |
96 | - else: | |
97 | - assert False, "not whoops" | |
124 | + assert False, "not whoops" |
@@ -8,7 +8,7 @@ from rpython.rlib.rfile import create_stdio | ||
8 | 8 | from cammylib.arrows import buildArrow, BuildProblem |
9 | 9 | from cammylib.hive import Hive, MissingAtom |
10 | 10 | from cammylib.parser import parse |
11 | -from cammylib.types import ConstraintStore, formatType, UnificationFailed | |
11 | +from cammylib.types import ConstraintStore, TypeExtractor, UnificationFailed | |
12 | 12 | |
13 | 13 | LINE_BUFFER_LENGTH = 1024 |
14 | 14 |
@@ -42,7 +42,8 @@ def repl(hive, stdin, stdout): | ||
42 | 42 | print "Arrow:", arrow |
43 | 43 | cs = ConstraintStore() |
44 | 44 | domain, codomain = arrow.types(cs) |
45 | - print "Type:", formatType(cs.walk(domain)), "->", formatType(cs.walk(codomain)) | |
45 | + extractor = TypeExtractor() | |
46 | + print "Type:", extractor.extractType(cs, domain), "->", extractor.extractType(cs, codomain) | |
46 | 47 | except BuildProblem as bp: |
47 | 48 | print "Couldn't build arrow:", bp.message |
48 | 49 | except UnificationFailed as uf: |
@@ -1,7 +1,10 @@ | ||
1 | 1 | import os |
2 | 2 | import os.path |
3 | 3 | |
4 | +from cammylib.arrows import buildArrow, BuildProblem | |
5 | +from cammylib.hive import Hive, MissingAtom | |
4 | 6 | from cammylib.parser import parse |
7 | +from cammylib.types import ConstraintStore, TypeExtractor, UnificationFailed | |
5 | 8 | |
6 | 9 | |
7 | 10 | def codeblock(code): |
@@ -9,6 +12,7 @@ def codeblock(code): | ||
9 | 12 | |
10 | 13 | def main(argv): |
11 | 14 | hivepath = argv[-1] |
15 | + hive = Hive(hivepath) | |
12 | 16 | if not hivepath.endswith(os.sep): |
13 | 17 | hivepath += os.sep |
14 | 18 | prefix = len(hivepath) |
@@ -29,6 +33,22 @@ def main(argv): | ||
29 | 33 | |
30 | 34 | doc.append("## " + atom) |
31 | 35 | doc.append(codeblock(sexp.asStr())) |
36 | + try: | |
37 | + sexp = sexp.canonicalize(hive) | |
38 | + arrow = buildArrow(sexp) | |
39 | + cs = ConstraintStore() | |
40 | + domain, codomain = arrow.types(cs) | |
41 | + extractor = TypeExtractor() | |
42 | + doc.append("Type: %s -> %s" % (extractor.extractType(cs, domain), | |
43 | + extractor.extractType(cs, codomain))) | |
44 | + except MissingAtom as ma: | |
45 | + doc.append( | |
46 | + "(Couldn't canonicalize due to missing expression %s)" % | |
47 | + ma.atom) | |
48 | + except BuildProblem: | |
49 | + doc.append("(Couldn't build arrow)") | |
50 | + except UnificationFailed as uf: | |
51 | + doc.append("(Couldn't typecheck: %s)" % uf.message) | |
32 | 52 | if trail: |
33 | 53 | doc.append(trail) |
34 | 54 | print "\n\n".join(doc) |