-
Notifications
You must be signed in to change notification settings - Fork 1
/
NOTES
443 lines (355 loc) · 17.8 KB
/
NOTES
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
4/26/2018
- I'm trying to get a simple example compiling fib as a string to work. It
appears I need the full Prelude (need "failure," for instance, and surely
more).
- I also need "otherwise," but I can work around that for now.
- To get the prelude, I will copy from CMC/runtime/lib. But there is already a
Prelude module in the interpreter that defines the built-ins. I somehow need
to resolve this.
- Maybe give the built-ins a special name like _Prelude. And then merge
the two with custom code.
- Or is there an equivalent to 'from _Prelude import *' that I can put in
the real prelude? I don't think so, because the Prelude actually
forward-declares, with "external," the things it expects the
implementation to provide.
TODO
- Clean up the name Prelude from system.py
- Change the name of system.py. It looks like a Python module, but it is
really a Curry module.
- It seems annoying to import _System and then copy everything into the
Prelude. All references to _System would need to be updated. Perhaps it
would be better to call the _System module Prelude after all, and then 1)
import it, 2) delete it from interp.modules, 3) import the "real" Prelude,
and 4) merge the contents.
4/29/2018
- I should split up and rename tests/test_py_target.py. A lot of things in
there are not specifically related to the Python backend. Those are really
interpreter tests that don't relate the the backend at all. Furthermore, the
file is just too big and unweildy. For now, I'm taking the step to create a
new file to test Prelude.apply. I think it would be better to make a
directory containing interpreter/test_*.py files.
5/1/2018
- I got this explanation regarding flexible/rigid tables from Sergio:
The meaning of flexible/rigid (it applies ot both a case and a function)
is whether a variable can be instantiated by a pattern. Consider the
following program:
mynot x = case x of {True -> False; False -> True}
main = mynot x where x free
Since the case is rigid the evaluation of main suspends.
However, if you replace "case" with "fcase" the evaluation of main
produces both False and True because x is bound to True and False,
respectively.
5/2/2018
- I fixed an error in the icurry _Base class. There were comparison methods,
but no hash. I took a while to track that one down.
- I'm working on adding support for ICurry BTables. I got down to the place
in function_compiler.py where the BTable just needs to be handled The code
will be similar to the code handling ATables.
5/3/2018
- I added the sendMoreMoney cryptarithmetic puzzle. It is a good test for
residuation: with residuation it finishes instantly whereas a narrowing
strategy exhausts all memory. Sergio suggests the following implementation:
In a flex case, you have code to instantiate a variable. In a rigid
case, you could have code to put the expression at the end of the queue.
If in the queue you have only expressions that were put there for this
reason, the program suspends.
5/5/2018
- I finished up the BTable code in function_compiler.py. The BTables can be
compiled and run.
- I refactored the unit tests. test_py_target was too big, so I broke it into
several smaller files for testing interpretation, compiling, and the runtime
aspects.
- I reworked the isa function a little and added helpers like isa_list and
isa_tuple.
BUGS:
- I noticed that test_atable and similar tests are slow even in subsequent
runs. I don't think curry2json should be invoked if the .json file already
exists, so this is unexpected.
- After I change a .curry file, curry.import_ fails to rerun curry2json. I
was using test_atable in test_py_evaluation.py.
-When curry2json fails, there is no informative error message.
NEXT:
- I should pare down the prelude so it can be loaded and then try to get the
fib program working.
5/9/2018
- I started trying to cut down the Prelude, but quickly ran into the issue from
5/5. I fixed the importer module so that the JSON is rebuilt when the source
file is newer.
5/20/2018
- In conversions.py, I'd really like to use Python 3 keyword-only parameters.
def expr(interp, arg, *args, target=None)
Maybe it's worth migrating to Python 3 if it's not difficult.
- I need to clean up the typeinfo stuff. The naming is not consistent. In
Interpreter there are things like ti_Failure, but it refers not to the typeinfo, but to the info table.
5/22/2018
- Working on docs and minor fixes that I come across in so doing.
5/23/2018
- I'm fixing the imports when compiling Curry expressions. To import a dynamic
module, it is necessary to keep its temporary directory around and update the
CURRYPATH temporarily if an expression requires that module.
TODO:
- Test the changes to Interpreter.import. It now accepts a currypath argument.
5/24/2018
- I checked in some changes to CMC to append to rather than overwrite CURRYPATH.
- The import system needs further modifications. Chapter 6 of the Curry report
describes the import features. I need to support symbol hiding, for one
thing. Also, symbols added by CMC appear in the main module. Once symbols
can be hidden, it would be nice to hide those as well.
5/25/2018
- I implemented what symbol-hiding I could without ICurry indicating which
symbols are private.
5/26/2018
- I did some general clean up. I renamed TypeInfo to NodeInfo and made some
variable names involving it more consistent. I tweaked the function for
skipping FWD nodes (it should tolerate unboxed values, now, though I hadn't
run into that limiatation yet). I consolidated the various functions for
node construction and rewriting.
9/6/2018
- I've been working hard on implementing constraints, but not using these
notes. I think it's a good idea to organize my thoughts, since the changes
lately have tended to get quite complex. I have been laboring under the
faulty understanding that something like x == y where x,y free should
instantiate variables whenever possible. So, if the type of x is known to
be, say Bool, then both x and y should be instantiated as Boolean generators.
Actually, I need residuation here. No matter what is known about these
variables, this expression should always suspend until one of these is
actually instantiated (in the proper context).
As for my next steps, I'd really like to make progress on implementing ==.
But given the above, it seems I need to implement residuation first. Here
are a few things that are small enough to bite off.
1. Implement residuation. Suppose the execution encounters x == y where
x,y free. I need to make a special Frame and place it at the end of the
queue. Its ``expr`` is just the == node. When the frame is activated,
it attempts a step. If it succeeds, it unblocks the original frame,
which is then added to the queue. If all frames in the queue are of
this type and none can make progress, then the computation fails with
suspended constraints.
2. Reorganize some code. The runtime module is getting too large. Also,
the unit tests should have some directory structure and perhaps common
subclasses. It seems there are too may ways of doing the same things.
9/12/2018
- I've been reworking the core evaluation methods to more closely match the FS.
Still, there are extensions for free variables, constraints, and residuation.
There are many offshoots to circle back to.
1. The Prelude functions implemented via py.func need to be reviewed. The
handler for that tag normalizes each successors, but that's often too
strict. It's probably better to just do the normalization manually in
each implementation function to avoid making that type of mistake.
2. Prelude.unknown returns a FWD node. The FWD nodes can always be removed
by copying the successors.
3. The debug source files that are written for PDB should also dump the
ICurry.
10/24/2020
- I'm going through the stashed code to clean up. Most of the code is already
in the master, and I'd rather just use this worklog instead of a branch to
keep snips that might be useful later. Here is something:
class Constraints(object):
def __init__(self, arg=None):
if arg is None:
- # defaultdict = collections.defaultdict
- # # Holds the variable bindings. There are two types: lazy and normal. A
- # # lazy binding is between a variable and an unevaluated expression, used
- # # to implement =:<= for functional patterns. A normal binding is between
- # # two free variables, used to implement =:=. These bindings imply
- # # additional bindings between successor variables. For example, if x_0
- # # :=: x_1 for two list variables, then after annotating x_0 = ([] ?_0
- # # (x_2:x_3)) and x_1 = ([] ?_1 (x_4:x_5)), we have two new bindings x_2
- # # :=: x_4 and x_3 :=: x_5.
# self.vars = Shared(lambda: defaultdict(lambda: Shared(list)))
- self.choices = Shared(ChoiceStore)
+ self.vars = Shared(UnionFind)
+ self.choices = Shared(UnionFind)
else:
- # self.vars = arg.vars
+ self.vars = arg.vars
self.choices = copy(arg.choices)
def __copy__(self):
10/25/2020
- This one seems to be an optimization for variable instantiation. It is not
needed because pull-tabbing through the =:= operator can work out which
constructors to use.
+def _buildInstanceConstraint(interp, instance, cidx, N, constraints):
+ '''
+ Given an instance (generator) and constructor index, build the choice
+ constraints needed to select that instance.
+ '''
+ if nctors == 1:
+ return constraints
+ else:
+ middle = -(-N // 2) # e.g., 7 -> 4.
+ cid = instance[0]
+ if cidx < middle:
+ constraints = _buildInstanceConstraint(
+ instance[1], cidx, middle, constraints
+ )
+ return Node(
+ interp.prelude._Constraint
+ , constraints
+ # Same here: can't use Pair.
+ , Node(interp.prelude.Pair, cid, LEFT)
+ )
+ else:
+ constraints = _buildInstanceConstraint(
+ instance[2], cidx-middle, N-middle, constraints
+ )
+ return Node(
+ interp.prelude._Constraint
+ , constraints
+ # Same here: can't use Pair.
+ , Node(interp.prelude.Pair, cid, RIGHT)
+ )
+
+
+def buildInstanceConstraint(interp, instance, cidx, typedef):
+ constructors = getattr(typedef, 'constructors', typedef)
+ N = len(constructors)
+ assert cidx < N
+ assert instance.info.tag = T_CHOICE
+ if N == 1: # (C ? failure)
+ return Node(
+ interp.prelude._Constraint
+ , Node(interp.prelude.True)
+ # FIXME: need to distinguish between node<=>node bindings and choice
+ # constraints, below. Probably shouldn't use a pair.
+ , Node(interp.prelude.Pair, instance[0], LEFT)
+ )
+ else:
+ return _buildInstanceConstraint(
+ interp, instance, cidx, N, Node(interp.prelude.True)
+ )
+
- Here we have code to bind a variable to a constructor. It serves a similar
purpose.
def _bindVarToCtor(self, interp, root, ctor, free):
assert ctor.info.tag >= runtime.T_CTOR
assert free.info.tag >= runtime.T_FREE
gpath = ctor.info.gpath
generator = runtime.getGenerator(interp, free, ctor.info.typedef())
# The generator always begins with a choice, even for types with one
# constructor.
assert len(gpath)
assert generator.info.tag == runtime.T_CHOICE
# 1. Equate subterms.
result = runtime.Node(
*self._eqSubterms(interp, root.info, ctor, generator[gpath])
)
# 2. Bind choices.
cids = tuple(runtime.get_ids(generator, gpath))
for gindex,cid in zip(gpath[:0:-1], cids[:0:-1]):
result = runtime.Node(
interp.prelude._ChoiceConstr.info
, result
, interp.expr((unboxed(cid), unboxed(runtime.gindex_to_lr(gindex))))
)
yield interp.prelude._ChoiceConstr.info
yield result
yield interp.expr(
(unboxed(cids[0]), unboxed(runtime.gindex_to_lr(gpath[0])))
)
- Here is code related to building paths through (Curry) generators.
def _build_gpath(ctor_id, num_ctors):
if num_ctors < 2:
# Special case for one constructor. The generator must begin with a choice,
# so the constructor is to the LEFT.
yield 0 # LEFT index
else:
while num_ctors > 1:
midpt = num_ctors - num_ctors // 2
if ctor_id < midpt:
num_ctors = midpt
yield 0 # LEFT index
else:
num_ctors -= midpt
ctor_id -= midpt
yield 1 # RIGHT index
def get_ids(generator, gpath):
for i in gpath:
assert generator.info.tag == T_CHOICE
yield get_id(generator)
generator = generator[i]
assert generator.info.tag >= T_CTOR
def gindex_to_lr(gindex):
'''Converts an index with a gpath (see InfoTable) to either LEFT or RIGHT.'''
assert gindex in (0,1)
return LEFT if gindex == 0 else RIGHT
And the update to the InfoTable for gpath:
- __slots__ = ['name', 'arity', 'tag', '_step', 'show', 'typecheck', 'typedef']
+ __slots__ = ['name', 'arity', 'tag', '_step', 'show', 'typecheck', 'typedef', 'gpath']
def __init__(self, name, arity, tag, step, show, typecheck):
.
.
.
+ # The path to this constructor in the corresponding generator. For
+ # instance, if the list type has generator (x:y) ? [], then constructor
+ # Const has gpath [0] and constructor Nil has gpath [1]. This is a normal
+ # path that can be used with Node.__getitem__, hence the indices 0 and 1.
+ self.gpath = None
And here are the test updates.
diff --git a/tests/unit_py_runtime.py b/tests/unit_py_runtime.py
index d01dab8..36e8aad 100644
--- a/tests/unit_py_runtime.py
+++ b/tests/unit_py_runtime.py
@@ -309,6 +309,13 @@ class TestInstantiation(cytest.TestCase):
instance = runtime.instantiate(interp, e, [0], interp.type('Type.T'))
au = curry.expr(*q(0, q(1, q(2, Type.A, Type.B), q(3, Type.C, Type.D)), q(4, q(5, Type.E, Type.F), Type.G)))
self.assertEqual(instance, au)
+ self.assertEqual(Type.A.info.gpath, (0,0,0))
+ self.assertEqual(Type.B.info.gpath, (0,0,1))
+ self.assertEqual(Type.C.info.gpath, (0,1,0))
+ self.assertEqual(Type.D.info.gpath, (0,1,1))
+ self.assertEqual(Type.E.info.gpath, (1,0,0))
+ self.assertEqual(Type.F.info.gpath, (1,0,1))
+ self.assertEqual(Type.G.info.gpath, (1,1))
def test_minDepth6(self):
'''Minimal instance depth (6 constructors).'''
@@ -321,6 +328,12 @@ class TestInstantiation(cytest.TestCase):
instance = runtime.instantiate(interp, e, [0], interp.type('Type.T'))
au = curry.expr(*q(0, q(1, q(2, Type.A, Type.B), Type.C), q(3, q(4, Type.D, Type.E), Type.F)))
self.assertEqual(instance, au)
+ self.assertEqual(Type.A.info.gpath, (0,0,0))
+ self.assertEqual(Type.B.info.gpath, (0,0,1))
+ self.assertEqual(Type.C.info.gpath, (0,1))
+ self.assertEqual(Type.D.info.gpath, (1,0,0))
+ self.assertEqual(Type.E.info.gpath, (1,0,1))
+ self.assertEqual(Type.F.info.gpath, (1,1))
def test_complex(self):
# ?0
@@ -332,3 +345,8 @@ class TestInstantiation(cytest.TestCase):
instance = runtime.instantiate(interp, e, [0], interp.type('Type.T'))
au = curry.expr(*q(0, q(1, q(2, Type.A, [Type.B, u]), [Type.C, u, u]), q(3, [Type.D, u, u, u], [Type.E, u, u, u, u])))
self.assertEqual(instance, au)
+ self.assertEqual(Type.A.info.gpath, (0,0,0))
+ self.assertEqual(Type.B.info.gpath, (0,0,1))
+ self.assertEqual(Type.C.info.gpath, (0,1))
+ self.assertEqual(Type.D.info.gpath, (1,0))
+ self.assertEqual(Type.E.info.gpath, (1,1))
07/10/2021
There is a new implementation of the Fair Scheme under
src/python/backends/py/runtime/fairscheme. Binding to integers is not
implemented and will probably result in a hang or "evaluation suspended"
message.
This is a faithful representation of the FS as published, with extensions for
free variables. The value of a computation can contain free variables.
Debugging
---------
To play with it, cd to tests/ and then run e.g., "./run_tests
func_examples.py". You can control the behavior by setting environment
variable SPRITE_INTERPRETER_FLAGS. Important options:
trace:true
You will get a trace of each step in the computation.
debug:true
The compile source code will be placed under .src/. You must use
this option to step into compiled code with PDB.
Example:
% SPRITE_INTERPRETER_FLAGS=trace:true,debug:true ./run_tests func_example.py
Call breakpoint() anywhere in the code to get an interactive prompt. Use
Ctrl-d to continue.
Call pdbtrace() anywhere to start a PDB prompt. You can go up and down the
stack. Use Ctrl-d to continue/exit.
Test Cases
----------
You can use the tests under tests/func_*.py. func_examples.py matches the
PDF files I sent before. func_eqconstr.py checks the equational constraints
quite thoroughly.
In a unit test under tests/func_*.py, use the RUN_ONLY and/or SKIP fields to
control which tests are run. Each accepts a regex.