-
Notifications
You must be signed in to change notification settings - Fork 3
/
dkanren.rkt
3598 lines (3438 loc) · 126 KB
/
dkanren.rkt
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
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#lang racket/base
(provide
==
=/=
absento
dk-evalo
fail
fresh
not-numbero
not-pairo
not-symbolo
numbero
symbolo
run
run*
succeed
term?
)
(require
racket/control
(except-in racket/list take)
(except-in racket/match ==)
racket/vector
)
(module+ test
(require
rackunit
))
; TODO:
; force remaining goals that are mentioned only in vattrs (e.g. disunify-or-suspend)
; unlike normal mk, all vars in =/=* should be tracked for earliest access to determinism
; these constraints can shrink domains, which may trigger new unifications, and so on
; implement quasiquote, let, let*, and, or, cond, match for evalo
; extended mk variant, mixing in deterministic computation where possible
; tagging and pluggable solvers (one in particular)
; aggressive, parallel term guesser
; not complete on its own, but likely more effective for typical synthesis
; analayzes all eval goals for the same term, analyzing the different envs and results
; size-incrementing basic component enumeration
; literals, vars, car/cdrs of everything available in env
; then cons (only as backwards info flow suggests) and predicate applications of components
; note: some primitives may have been shadowed or renamed
; may introduce lambdas when backwards info flow indicates a closure is necessary
; identifying components useful as conditions (capable of partitioning the eval goals)
; not useful unless component expresses both #f and not #f at least once each across envs
; e.g. literals are not useful
; could perform this identification in parallel, for each component over each env
; defer components that are not available in all envs, until conditional splitting
; general size-incrementing component enumeration
; e.g. applying non-primitive procedures (try to avoid unnested self-application/non-termination)
; conditional splitting, producing lambdas, letrecs, etc.
; conditional splitting (if, cond, match, depending on shadowing/complexity)
; if none of if/cond/match are available, and no closures are capable of conditional splitting,
; could potentially refute this line of search early
; if there are basic components useful as conditions, try those before general components
; tagging and reification for =/=, absento
; could do this just for =/= and be satisfied with some infinite absento enumerations
; port everything to chez
; performance and benchmarking
; move from closure-encoding interpreter to ahead-of-time compiler
; tweak mk data reprs
; e.g. try path compression
; try relational arithmetic
; try first-order minicurry tests
; future
; de bruijn encoding interpreter
; could be a useful alternative to closure-encoding for 1st order languages
; try for both dkanren (for comparison) and relational interpreter (may improve perf)
; possible ways to improve performance
; infer impossible scrutinee domains and immediately distypify them (integrate with prefix factoring?)
; pattern prefix factoring (also affects run* termination)
; ideally we'd use full-blown pattern compilation
; but hard to preserve programmer's intended(?) clause search priority with nesting changes
; in contrast factoring only the final clauses will not affect priorities (since nothing follows them)
; match/r, reversible match: manually describe a reversed computation
; should be much better than falling back to denote-rhs-pattern-unknown
; auto-compacting, left-associative binds
; Left-associative binds are preferred because they allow rewards to be
; doled out every time a new conjunct is reached. The downside is that
; left-associative tree structures involve longer paths between the root
; and active leaf nodes of the search, and these paths are traversed down
; and up for every resumption/suspension. To mitigate this overhead we
; can compress contiguous left-associative conjunction chains into a single
; node, unpacking the next conjunct only once it's needed by a successful
; child result. This avoids the overhead along any portion of a chain that
; hasn't yet encountered a child success disjunction, but we'd also like to
; recognize when we can safely recompact. It's safe to recompact
; left-associative conjuncts when there are no intervening disjunctions.
; Intervening disjunctions may come and go either due to branch failure, or
; to a successful branch continually being promoted (it was originally
; promoted in, causing the disruption, but now succeeded again, so it's
; promoted up and out). If we track the type of suspension thunks that are
; returned to bind, to differentiate between (outermost) conjunction and
; disjunction thunks, we can gradually recompact: when returning a
; conjunction thunk to a conjunction, incorporate the chain of the returned
; conjunction into the parent.
; profiling results
; quine:
; match-chain-try loop: 58.6%, 27.0%
; pattern-exec-and: 54.2%, 2.1%
; thrine:
; match-chain-try loop: 84.3%, 23.1%
; pattern-exec-and: 77.5%, 4.6%
; pattern-assert-==: 32.1%, 21.4%
; append (cdr xs):
; state-resume-det loop1 94.6%, 0%
; match-chain-try loop: 79.7%, 45.5%
; append more:
; match-chain-try loop: 92.1%, 75.8%
; arithmetic:
; retry: 73.6%, 1.4%
; match-chain-try loop: 69.3%, 16.3%
; pattern-exec-and: 42.8%, 13.3%
; pattern-assert-or: 31.8%, 1.4%
; pattern-assert-==: 28.5%, 12.3%
; pattern-assert-pair: 27.6%, 0%
; unify: 17.6%, 10.8%
; val-depend: 6.8%
; "define record" for portability to chez
(define-syntax defrec
(syntax-rules ()
((_ name fnames ...) (struct name (fnames ...) #:transparent))))
(define-syntax let*/and
(syntax-rules ()
((_ () rest ...) (and rest ...))
((_ ((name expr) ne* ...) rest ...)
(let ((name expr))
(and name (let*/and (ne* ...) rest ...))))))
(define-syntax let/if
(syntax-rules ()
((_ (name expr) true-alt false-alt)
(let ((name expr)) (if name true-alt false-alt)))))
(define-syntax let/list
(syntax-rules ()
((_ loop ((ncar ncdr expr) binding ...) pair-alt else-alt)
(let loop ((npair expr) binding ...)
(match npair
(`(,ncar . ,ncdr) pair-alt)
('() else-alt))))))
(define store-empty (hasheq))
(define store-ref hash-ref)
(define store-set hash-set)
(define store-remove hash-remove)
(define store-keys hash-keys)
(define (list-add-unique xs v) (if (memq v xs) xs (cons v xs)))
(define (list-cons-unique x xs) (if (memq x xs) xs (cons x xs)))
(define (list-append-unique xs ys)
(if (null? xs) ys
(let ((zs (list-append-unique (cdr xs) ys))
(x0 (car xs)))
(if (memq x0 ys) zs (cons x0 zs)))))
(define (list-subtract xs ys)
(if (null? xs) xs
(let ((x0 (car xs))
(xs1 (list-subtract (cdr xs) ys)))
(if (memq x0 ys) xs1
(if (eq? xs1 (cdr xs)) xs
(cons x0 xs1))))))
(defrec var name)
(define var-0 (var 'initial))
(define (value->vars st val)
(match (walk1 st val)
(`(,a . ,d) (list-append-unique (value->vars st a) (value->vars st d)))
((? var? vr) (list vr))
(_ '())))
(define domain-full '(pair symbol number () #f #t))
(define (domain-remove dmn type) (remove type dmn))
(define (domain-has-type? dmn type)
(or (eq? domain-full dmn) (memq type dmn)))
(define (domain-has-val? dmn val)
(or (eq? domain-full dmn) (memq (match val
((? pair?) 'pair)
((? symbol?) 'symbol)
((? number?) 'number)
(_ val)) dmn)))
(define (domain-overlap? d1 d2)
(cond ((eq? domain-full d1) #t)
((eq? domain-full d2) #t)
(else (let loop ((d1 d1) (d2 d2))
(or (memq (car d1) d2)
(and (pair? (cdr d1)) (loop (cdr d1) d2)))))))
(define (domain-intersect d1 d2)
(cond ((eq? domain-full d1) d2)
((eq? domain-full d2) d1)
(else (let loop ((d1 d1) (d2 d2) (di '()))
(let* ((d1a (car d1))
(d1d (cdr d1))
(d2d (memq d1a d2))
(di (if d2d (cons (car d1) di) di))
(d2d (if d2d (cdr d2d) d2)))
(if (or (null? d1d) (null? d2d))
(if (null? di) #f (reverse di))
(loop d1d d2d di)))))))
; The state-vs will contain either vattr records or terms on the right hand side
(defrec vattr
domain ; type constraints, but not concrete values. Because we don't use vattrs for known terms, this domain
; will never be just #f or just #t or just ().
=/=s ; Disequality constraint information. Only has individual pairs, not =/=* info as in faster-miniKanren.
goals ; (list symbol) identifying entries in state-goals. (This indirection saves us from running a goal
; multiple times when it appears attached to different variables or otherwise is multiply scheduled).
; Includes any goal for which this variable is a blocker, or for which this variable represents the result of
; the match statement represented by the goal
dependencies ; (list symbol) identifying entries in state-goals
; A subset of goals, just including the goals for which this variable represents the result of
; the match statement represented by the goal except for those that have not been scheduled
; as a result of dependency analysis. Gets smaller as scheduling sets up execution with binds.
)
(define (vattrs-get vs vr) (store-ref vs vr vattr-empty))
(define vattrs-set store-set)
(define vattr-empty (vattr domain-full '() '() '()))
(define (vattr-domain-set va dmn)
(vattr dmn (vattr-=/=s va) (vattr-goals va) (vattr-dependencies va)))
(define (vattr-=/=s-clear va)
(vattr (vattr-domain va) '() (vattr-goals va) (vattr-dependencies va)))
(define (state-vattr-=/=s-add st vr va val)
(if (or (var? val) (domain-has-val? (vattr-domain va) val))
(let ((=/=s (vattr-=/=s va)))
(if (memq val =/=s) st
(state-var-set
st vr (vattr (vattr-domain va)
(cons val =/=s)
(vattr-goals va)
(vattr-dependencies va)))))
st))
(define (vattr-=/=s-add va val)
(if (or (var? val) (domain-has-val? (vattr-domain va) val))
(let ((=/=s (vattr-=/=s va)))
(if (memq val =/=s) va
(vattr (vattr-domain va)
(cons val =/=s)
(vattr-goals va)
(vattr-dependencies va))))
va))
(define (vattr-=/=s-has? va val) (memq val (vattr-=/=s va)))
(define (vattr-associate va goal)
(vattr (vattr-domain va)
(vattr-=/=s va)
(list-cons-unique goal (vattr-goals va))
(vattr-dependencies va)))
(define (vattr-associate* va goals)
(vattr (vattr-domain va)
(vattr-=/=s va)
(list-append-unique goals (vattr-goals va))
(vattr-dependencies va)))
(define (vattr-depend va goal)
(vattr (vattr-domain va)
(vattr-=/=s va)
(list-cons-unique goal (vattr-goals va))
(list-cons-unique goal (vattr-dependencies va))))
(define (vattr-depend* va goals)
(vattr (vattr-domain va)
(vattr-=/=s va)
(list-append-unique goals (vattr-goals va))
(list-append-unique goals (vattr-dependencies va))))
(define (vattr-goals-clear va)
(vattr (vattr-domain va) (vattr-=/=s va) '() (vattr-dependencies va)))
(define (vattr-dependencies-clear va)
(vattr (vattr-domain va) (vattr-=/=s va) (vattr-goals va) '()))
(define (vattr-overlap? va1 va2)
(domain-overlap? (vattr-domain va1) (vattr-domain va2)))
(define (vattr-intersect va1 va2)
(let*/and ((di (domain-intersect (vattr-domain va1) (vattr-domain va2))))
(vattr di
(list-append-unique (vattr-=/=s va1) (vattr-=/=s va2))
(append (vattr-goals va1) (vattr-goals va2))
(append (vattr-dependencies va1)
(vattr-dependencies va2)))))
; These appear as values in state-goals
; All goal-suspendeds are created as the result of a suspended match
(defrec goal-suspended
tag ; Not yet used. Intent is that user could tag match statements with arbitrary data that says
; what kind of expression it is. Then a meta-process could inspect the goal store and group goals
; by type for its reasoning and rescheduling purposes, or solving with a domain-specific solver
result ; Used for the same purpose as tag, but stores the result of the underlying match statement.
blockers ; Variables, that if bound, may allow this goal to run deterministically. Any that are unified will
; trigger this goal for a deterministic attempt.
retry ; Goal (function taking state) to run to resume the goal execution. This one will only attempt deterministic
; computation, and will suspend if a guess must be made.
guess ; Goal (function taking state) to run to resume the goal execution. This one will make a guess and create an
; mplus subtree.
active? ; If #f, then this is a lazy match
)
(define (goal-ref-new) (gensym))
(define (goal-retry goals goal)
(if (procedure? goal) goal
(let*/and ((gsusp (store-ref goals goal #f)))
(goal-suspended-retry gsusp))))
(define (goal-block-cons block blocks)
(if (null? block) blocks (cons block blocks)))
(defrec schedule det nondet)
(define schedule-empty (schedule '() '()))
(define (schedule-add-det sch det)
(schedule (goal-block-cons det (schedule-det sch))
(schedule-nondet sch)))
(define (schedule-add-nondet sch nondet)
(schedule (schedule-det sch)
(goal-block-cons nondet (schedule-nondet sch))))
(define (schedule-add sch det nondet)
(schedule (goal-block-cons det (schedule-det sch))
(goal-block-cons nondet (schedule-nondet sch))))
(define (schedule-clear-det sch)
(schedule '() (schedule-nondet sch)))
(defrec state vs goals schedule)
(define state-empty (state store-empty store-empty schedule-empty))
(define (state-var-get st vr) (vattrs-get (state-vs st) vr))
(define (state-var-set st vr va)
(state (vattrs-set (state-vs st) vr va)
(state-goals st)
(state-schedule st)))
(define (state-suspend st forward? vr goal)
(state-var-set
st vr (let ((va (state-var-get st vr)))
(if forward?
(vattr-associate va goal)
(vattr-depend va goal)))))
(define (state-suspend* st var-forwards var-backwards goal-ref goal)
(define (update vattr-add vs vrs)
(foldl (lambda (vr vs)
(vattrs-set vs vr (vattr-add (vattrs-get vs vr) goal-ref)))
vs vrs))
(let* ((goals (store-set (state-goals st) goal-ref goal))
(vs (state-vs st))
(vs (update vattr-associate vs var-forwards) )
(active? (and (goal-suspended? goal) (goal-suspended-active? goal)))
(vs (update
(if active? vattr-depend vattr-associate) vs var-backwards)))
(state vs goals (let ((sch (state-schedule st)))
(if (and (null? var-backwards) active?)
(schedule-add-nondet sch (list goal-ref))
sch)))))
(define (state-goals-set st goals)
(state (state-vs st) goals (state-schedule st)))
(define (state-remove-goal st goal)
(state-goals-set st (store-remove (state-goals st) goal)))
(define (state-schedule-clear-det st)
(state (state-vs st) (state-goals st) (schedule-clear-det
(state-schedule st))))
(define (state-schedule-set st sch) (state (state-vs st) (state-goals st) sch))
(define (state-schedule-clear st) (state-schedule-set schedule-empty))
(define det-quota 200)
(define det-cost 0)
(define (det-reset) (set! det-cost 0))
(define (det-pay cost)
(let ((next-cost (+ cost det-cost)))
(if (> det-quota next-cost)
(set! det-cost next-cost)
(begin (set! det-cost 0) (shift k k)))))
(define-syntax reset-cost
(syntax-rules ()
((_ body ...) (let ((result (reset body ...)))
(det-reset)
result))))
(define (state-resume-det1 st)
(let* ((det (schedule-det (state-schedule st)))
(st (state-schedule-clear-det st)))
(let/list loop ((goals det det) (st st))
(let/list loop1 ((goal goals goals) (st st))
(let/if (retry (goal-retry (state-goals st) goal))
(let*/and ((st (retry st))
(st (state-resume-det1 st)))
(loop1 goals st))
(loop1 goals st))
(loop det st))
st)))
(define (state-resume-nondet1 st)
(let* ((sgoals (state-goals st))
(nondet (schedule-nondet (state-schedule st))))
(let/list loop ((goals nondet (reverse nondet)) (vs (state-vs st)))
(let/list loop1 ((goal goals1 goals))
(let/if (gsusp (store-ref sgoals goal #f))
(let/list loop2 ((blocker blockers (goal-suspended-blockers gsusp)))
(let-values (((blocker va) (walk-vs vs blocker)))
(let ((deps (vattr-dependencies va)))
(if (null? deps) (loop2 blockers)
(loop
(cons deps (cons (cons goal goals1) nondet))
(vattrs-set vs blocker (vattr-dependencies-clear va))))))
(let ((sch-next (schedule '() (reverse (cons (cons goal goals1) nondet)))))
; Interesting bit for (demanded) search tree structure!
; Greg thinks this would better as a left-associative bind instead of
; the right-associative one resulting from the current recursion structure.
; That is, (bind (bind (bind guess resume-pending) sch-next(0)) sch-next(1)) etc
(bind
(bind ((goal-suspended-guess gsusp)
(state vs sgoals schedule-empty))
state-resume-pending)
(lambda (st)
(state-resume-nondet1 (state-schedule-set st sch-next))))))
(loop1 goals1))
(loop nondet vs))
(state vs sgoals schedule-empty))))
(define (state-resume-pending st)
(bind (reset-cost (state-resume-det1 st)) state-resume-nondet1))
; This runs goals that are not demanded
(define (state-resume-remaining st)
(define sgoals (state-goals st))
(define all (store-keys sgoals))
(let/list loop ((goal-ref goal-refs all) (active '()))
(let ((gsusp (store-ref sgoals goal-ref)))
(if (goal-suspended-active? gsusp)
(loop goal-refs (cons goal-ref active))
(loop goal-refs active)))
(if (null? active)
(if (null? all) st
; TODO: also force remaining unnamed goals from vattrs
(bind
(state-resume-nondet1
(state-schedule-set st (schedule '() (list all))))
state-resume-remaining))
; If there's undemanded by not lazy stuff, do it. Currently no hierarchy between them.
(bind (state-resume-nondet1
(state-schedule-set st (schedule '() (list active))))
state-resume-remaining))))
; First run everything demanded; only after those and those they generate are done, then do undemanded
(define (state-resume st)
(bind (state-resume-pending st) state-resume-remaining))
(define (state-var-domain-set st vr va dmn)
(match dmn
(`(,(and (not 'symbol) (not 'number) singleton))
(state-var-== st vr va (if (eq? 'pair singleton)
`(,(var 'pair-a) . ,(var 'pair-d)) singleton)))
('() #f)
(dmn (let ((st (state-var-set st vr (vattr-domain-set va dmn))))
(if (eq? domain-full dmn) st
(state (state-vs st)
(state-goals st)
(schedule-add (state-schedule st)
(vattr-goals va)
(vattr-dependencies va))))))))
(define (state-var-domain-== st vr va dmn)
(let*/and ((dmn (domain-intersect (vattr-domain va) dmn)))
(state-var-domain-set st vr va dmn)))
(define (state-var-type-== st vr va type)
(and (domain-has-type? (vattr-domain va) type)
(state-var-domain-set st vr va `(,type))))
(define (state-var-type-=/= st vr va type)
(state-var-domain-set st vr va (domain-remove (vattr-domain va) type)))
;; Ideally we would notify any vars in =/=s that they can drop 'vr' from their
;; own =/=s, but not doing so shouldn't be a big deal. Same story when
;; handling state-var-==-var.
(define (state-var-== st vr va val)
(cond
((eq? vattr-empty va) (state-var-set st vr val))
((domain-has-val? (vattr-domain va) val)
(let ((=/=s (vattr-=/=s va)))
(and (not (memq val =/=s))
(let* ((vps (filter (if (pair? val)
(lambda (x) (or (var? x) (pair? x)))
var?) =/=s))
(deps (vattr-dependencies va))
(vs (if (null? deps) (state-vs st)
(let val-depend ((vs (state-vs st)) (val val))
(match val
(`(,pa . ,pd) (val-depend (val-depend vs pa) pd))
((? var? evr)
(let-values (((evr eva) (walk-vs vs evr)))
(if (var? evr)
(store-set vs evr (vattr-depend* eva deps))
(val-depend vs evr))))
(_ vs)))))
(st (state (store-set vs vr val)
(state-goals st)
(schedule-add (state-schedule st)
(vattr-goals va)
deps))))
(disunify* st val vps)))))
(else #f)))
(define (state-var-=/= st vr va val)
(if (or (eq? '() val) (eq? #f val) (eq? #t val))
(state-var-type-=/= st vr va val)
(state-vattr-=/=s-add st vr va val)))
(define (state-var-==-var st vr1 va1 vr2 va2)
(let*/and ((va (vattr-intersect va1 va2)))
(let* ((=/=s (vattr-=/=s va))
(va (vattr-=/=s-clear va))
(st (state-var-set st vr1 vr2))
(st (state (state-vs st)
(state-goals st)
(schedule-add-det (state-schedule st) (vattr-goals va))))
(va (vattr-goals-clear va)))
(disunify* (state-var-set st vr2 va) vr2 =/=s))))
(define (state-var-=/=-var st vr1 va1 vr2 va2)
(if (vattr-overlap? va1 va2)
(state-vattr-=/=s-add (state-vattr-=/=s-add st vr1 va1 vr2) vr2 va2 vr1)
st))
(define (state-var-=/=-redundant? st vr va val)
(or (not (domain-has-val? (vattr-domain va) val))
(vattr-=/=s-has? va val)))
(define (state-var-=/=-var-redundant? st vr1 va1 vr2 va2)
(or (not (vattr-overlap? va1 va2))
(vattr-=/=s-has? va1 vr2)
(vattr-=/=s-has? va2 vr1)))
(define (walk-vs vs vr)
(let ((va (vattrs-get vs vr)))
(cond ((vattr? va) (values vr va))
((var? va) (walk-vs vs va))
(else (values va #f)))))
(define (walk st tm)
(if (var? tm)
(walk-vs (state-vs st) tm)
(values tm #f)))
(define (walk1 st tm) (let-values (((val va) (walk st tm))) val))
(define (not-occurs? st vr tm)
(if (pair? tm) (let-values (((ht _) (walk st (car tm))))
(let*/and ((st (not-occurs? st vr ht)))
(let-values (((tt _) (walk st (cdr tm))))
(not-occurs? st vr tt))))
(if (eq? vr tm) #f st)))
(define (unify st v1 v2)
(let-values (((v1 va1) (walk st v1))
((v2 va2) (walk st v2)))
(cond ((eq? v1 v2) st)
((var? v1) (if (var? v2)
(state-var-==-var st v1 va1 v2 va2)
(and (not-occurs? st v1 v2)
(state-var-== st v1 va1 v2))))
((var? v2) (and (not-occurs? st v2 v1)
(state-var-== st v2 va2 v1)))
((and (pair? v1) (pair? v2))
(let*/and ((st (unify st (car v1) (car v2))))
(unify st (cdr v1) (cdr v2))))
(else #f))))
(define (disunify* st v1 vs)
(if (null? vs) st (let*/and ((st (disunify st v1 (car vs))))
(disunify* st v1 (cdr vs)))))
(define (disunify st v1 v2) (disunify-or st v1 v2 '()))
(define (disunify-or-suspend st v1 va1 v2 pairings)
(state-suspend st #t v1 (lambda (st) (disunify-or st v1 v2 pairings))))
(define (disunify-or st v1 v2 pairings)
(let-values (((v1 va1) (walk st v1)))
(disunify-or-rhs st v1 va1 v2 pairings)))
(define (disunify-or-rhs st v1 va1 v2 pairings)
(let-values (((v2 va2) (walk st v2)))
(cond
((eq? v1 v2)
(and (pair? pairings)
(disunify-or st (caar pairings) (cdar pairings) (cdr pairings))))
((var? v1)
(if (null? pairings)
(if (var? v2)
(state-var-=/=-var st v1 va1 v2 va2)
(state-var-=/= st v1 va1 v2))
(if (var? v2)
(if (state-var-=/=-var-redundant? st v1 va1 v2 va2) st
(if (state-var-=/=-var st v1 va1 v2 va2)
(disunify-or-suspend st v1 va1 v2 pairings)
(disunify-or
st (caar pairings) (cdar pairings) (cdr pairings))))
(if (state-var-=/=-redundant? st v1 va1 v2) st
(if (state-var-=/= st v1 va1 v2)
(disunify-or-suspend st v1 va1 v2 pairings)
(disunify-or
st (caar pairings) (cdar pairings) (cdr pairings)))))))
((var? v2)
(if (null? pairings)
(state-var-=/= st v2 va2 v1)
(if (state-var-=/=-redundant? st v2 va2 v1) st
(if (state-var-=/= st v2 va2 v1)
(disunify-or-suspend st v2 va2 v1 pairings)
(disunify-or
st (caar pairings) (cdar pairings) (cdr pairings))))))
((and (pair? v1) (pair? v2))
(disunify-or
st (car v1) (car v2) (cons (cons (cdr v1) (cdr v2)) pairings)))
(else st))))
(define (typify st type val)
(let-values (((val va) (walk st val)))
(if (var? val) (state-var-type-== st val va type)
(and (match type
('symbol (symbol? val))
('number (number? val))
('pair (pair? val))
(_ (eq? type val)))
st))))
(define (distypify st type val)
(let-values (((val va) (walk st val)))
(if (var? val) (state-var-type-=/= st val va type)
(and (not (match type
('symbol (symbol? val))
('number (number? val))
('pair (pair? val))
(_ (eq? type val))))
st))))
(define (domainify st val dmn)
(if (eq? domain-full dmn) st
(let-values (((val va) (walk st val)))
(if (var? val)
(state-var-domain-== st val va dmn)
(and (domain-has-val? dmn val) st)))))
(define (domainify* st val dmn*)
(define d-pair (cdr dmn*))
(let*/and ((st1 (domainify st val (car dmn*))))
(if (pair? d-pair)
(let ((val (walk1 st1 val)))
(if (pair? val)
(let*/and ((st2 (domainify* st1 (car val) (car d-pair))))
(domainify* st2 (cdr val) (cdr d-pair)))
st1))
st1)))
(define (succeed st) st)
(define (fail st) #f)
(define (== t0 t1) (lambda (st) (unify st t0 t1)))
(define (=/= t0 t1) (lambda (st) (disunify st t0 t1)))
(define (symbolo tm) (lambda (st) (typify st 'symbol tm)))
(define (numbero tm) (lambda (st) (typify st 'number tm)))
(define (not-symbolo tm) (lambda (st) (distypify st 'symbol tm)))
(define (not-numbero tm) (lambda (st) (distypify st 'number tm)))
(define (not-pairo tm) (lambda (st) (distypify st 'pair tm)))
(define (absento atom tm)
(dk-evalo
`(letrec ((absent?
(lambda (tm)
(match/lazy tm
(`(,ta . ,td) (and (absent? ta) (absent? td)))
(',atom #f)
(_ #t)))))
(absent? ',tm))
#t))
(define-syntax zzz (syntax-rules () ((_ body ...) (lambda () body ...))))
(define (mplus ss zss)
(match ss
(#f (zss))
((? procedure?) (zzz (mplus (zss) ss)))
((? state?) (cons ss zss))
(`(,result . ,zss1) (cons result (zzz (mplus (zss) zss1))))))
(define (take n ss)
(if (and n (zero? n)) '()
(match ss
(#f '())
((? procedure?) (take n (ss)))
((? state?) (list ss))
(`(,result . ,ss) (cons result (take (and n (- n 1)) ss))))))
(define (bind ss goal)
(match ss
(#f #f)
((? procedure?) (zzz (bind (ss) goal)))
((? state?) (goal ss))
(`(,result . ,zss) (mplus (goal result) (zzz (bind (zss) goal))))))
(define-syntax bind*
(syntax-rules ()
((_ e) e)
((_ e goal0 goal ...) (bind* (bind e goal0) goal ...))))
(define-syntax let/vars
(syntax-rules ()
((_ (vname ...) body ...)
(let ((vname (var (gensym (symbol->string 'vname)))) ...) body ...))))
(define-syntax fresh
(syntax-rules ()
((_ (vname ...) goal ...)
(let/vars (vname ...) (lambda (st) (bind* st goal ...))))))
(define (reify-var ix) (string->symbol (string-append "_." (number->string ix))))
(define (reify tm)
(lambda (st)
(let-values
(((st ixs tm)
(let loop ((st st) (ixs store-empty) (tm tm))
(let-values (((tm va) (walk st tm)))
(cond
((var? tm)
(let/if (ix (store-ref ixs tm #f))
(values st ixs (reify-var ix))
(let ((ix (hash-count ixs)))
(values st (store-set ixs tm ix) (reify-var ix)))))
((pair? tm)
(let*-values (((st ixs thd) (loop st ixs (car tm)))
((st ixs ttl) (loop st ixs (cdr tm))))
(values st ixs (cons thd ttl))))
(else (values st ixs tm)))))))
tm)))
(define-syntax run
(syntax-rules ()
((_ n (qv ...) goal ...)
(map (reify var-0)
(take n (zzz ((fresh (qv ...)
(== (list qv ...) var-0) goal ... state-resume)
state-empty)))))))
(define-syntax run* (syntax-rules () ((_ body ...) (run #f body ...))))
(define (quotable? v)
(match v
(`(,a . ,d) (and (quotable? a) (quotable? d)))
((? procedure?) #f)
(_ #t)))
(define (rev-append xs ys)
(if (null? xs) ys (rev-append (cdr xs) (cons (car xs) ys))))
(define-syntax let*/state
(syntax-rules ()
((_ () body ...) (begin body ...))
((_ (((st val) expr) bindings ...) body ...)
(let-values (((st val) expr))
(if st
(let*/state (bindings ...) body ...)
(values #f #f))))))
(define (goal-value val) (lambda (st) (values st val)))
(define (denote-value val) (lambda (env) (goal-value val)))
(define denote-true (denote-value #t))
(define denote-false (denote-value #f))
(define (denote-lambda params body senv)
(match params
((and (not (? symbol?)) params)
(let ((dbody (denote-term body (extend-env* params params senv)))
(nparams (length params)))
(lambda (env)
(goal-value
(lambda args
(let ((nargs (length args)))
(when (not (= nparams nargs))
(error
(format
"expected ~a args, given ~a; params=~v, body=~v, args=~v"
nparams nargs params body args))))
(dbody (rev-append args env)))))))
(sym
(let ((dbody (denote-term body `((val . (,sym . ,sym)) . senv))))
(lambda (env) (goal-value (lambda args (dbody (cons args env)))))))))
(define (denote-variable sym senv)
(let loop ((idx 0) (senv senv))
(match senv
(`((val . (,y . ,b)) . ,rest)
(if (equal? sym y)
(lambda (env) (goal-value (list-ref env idx)))
(loop (+ idx 1) rest)))
(`((rec . ,binding*) . ,rest)
(let loop-rec ((ridx 0) (binding* binding*))
(match binding*
('() (loop (+ idx 1) rest))
(`((,p-name ,_) . ,binding*)
(if (equal? sym p-name)
(lambda (env)
((list-ref (list-ref env idx) ridx) (drop env idx)))
(loop-rec (+ ridx 1) binding*))))))
('() (error (format "unbound variable: '~a'" sym))))))
(define (denote-qq qqterm senv)
(match qqterm
(`(,'unquote ,term) (denote-term term senv))
(`(,a . ,d) (let ((da (denote-qq a senv)) (dd (denote-qq d senv)))
(lambda (env)
(let ((ga (da env)) (gd (dd env)))
(lambda (st)
(let*/state (((st va) (ga st))
((st va) (actual-value st va #f #f))
((st vd) (gd st))
((st vd) (actual-value st vd #f #f)))
(values st `(,va . ,vd))))))))
((? quotable? datum) (denote-value datum))))
(define (denote-application proc a* senv)
(denote-apply (denote-term proc senv) (denote-term-list a* senv)))
(define (denote-apply dp da*)
(lambda (env)
(let ((gp (dp env)) (ga* (da* env)))
(lambda (st)
(let*/state (((st va*) (ga* st)) ((st vp) (gp st)))
((apply vp va*) st))))))
(define (denote-term-list terms senv)
(let ((d* (map (lambda (term) (denote-term term senv)) terms)))
(lambda (env)
(let ((g* (map (lambda (d0) (d0 env)) d*)))
(lambda (st)
(let loop ((st st) (xs '()) (g* g*))
(if (null? g*) (values st (reverse xs))
(let*/state (((st val) ((car g*) st))
((st val) (actual-value st val #f #f)))
(loop st (cons val xs) (cdr g*))))))))))
(define (denote-rhs-pattern-unknown env st v) st)
(define (denote-rhs-pattern-literal literal)
(lambda (env st v) (unify st literal v)))
(define (denote-rhs-pattern-var vname senv)
(let ((dv (denote-term vname senv)))
(lambda (env st rhs)
(let*/state (((st v) ((dv env) st))) (unify st v rhs)))))
(define (denote-rhs-pattern-qq qq senv)
(match qq
(`(,'unquote ,pat) (denote-rhs-pattern pat senv))
(`(,a . ,d)
(let ((da (denote-rhs-pattern-qq a senv))
(dd (denote-rhs-pattern-qq d senv)))
(lambda (env st v)
(let/vars (va vd)
(let ((v1 `(,va . ,vd)))
(let*/and ((st (unify st v v1)) (st (da env st va)))
(dd env st vd)))))))
((? quotable? datum) (denote-rhs-pattern-literal datum))))
(define denote-rhs-pattern-true (denote-rhs-pattern-literal #t))
(define denote-rhs-pattern-false (denote-rhs-pattern-literal #f))
(define (denote-rhs-pattern pat senv)
(match pat
(`(quote ,(? quotable? datum)) (denote-rhs-pattern-literal datum))
(`(quasiquote ,qq) (denote-rhs-pattern-qq qq senv))
((? symbol? vname) (denote-rhs-pattern-var vname senv))
((? number? datum) (denote-rhs-pattern-literal datum))
(#t denote-rhs-pattern-true)
(#f denote-rhs-pattern-false)
(_ denote-rhs-pattern-unknown)))
(define (extract-svs st v1 v2)
(let-values (((v1 va1) (walk st v1))
((v2 va2) (walk st v2)))
(match* (v1 v2)
(((? var?) (? var?)) (list v1 v2))
(((? var?) _) (list v1))
((_ (? var?)) (list v2))
((`(,a1 . ,d1) `(,a2 . ,d2))
(list-append-unique (extract-svs st a1 a2) (extract-svs st d1 d2)))
((_ _) '()))))
(define p-any '(_))
(define (p-lookup path) `(lookup ,path))
(define (p-literal datum) `(literal ,datum))
(define (p-type tag) `(type ,tag))
(define p-symbol (p-type 'symbol))
(define p-number (p-type 'number))
(define p-pair (p-type 'pair))
(define (p-car p) `(car ,p))
(define (p-cdr p) `(cdr ,p))
(define (p-and p1 p2) `(and ,p1 ,p2))
(define (p-or p1 p2) `(or ,p1 ,p2))
(define (p-not p) `(not ,p))
(define (p-? pred) `(? ,pred))
(define p-none (p-not p-any))
(define (p-and* p*)
(match p*
('() p-any)
(`(,p) p)
(`(,p . ,p*) (p-and p (p-and* p*)))))
(define (p-or* p*)
(match p*
('() p-none)
(`(,p) p)
(`(,p . ,p*) (p-or p (p-or* p*)))))
(define (datum->didx datum)
(match datum
(`(,_ . ,_) 0)
((? symbol?) 1)
((? number?) 2)
('() 3)
(#f 4)
(#t 5)))
(define (tag->didx tag)
(match tag
('pair 0)
('symbol 1)
('number 2)
('() 3)
(#f 4)
(#t 5)))
(define (p->domain parity p)
(define not-pair (pdomain-complement (pdomain-single (tag->didx 'pair))))
(define (paritize parity pd) (if parity pd (pdomain-complement pd)))
(define (paritize-pair parity ppd)
(if parity ppd (pdomain-join ppd not-pair)))
(match p
(`(literal ,datum)
(match datum
((or '() #f #t) (p->domain parity (p-type datum)))
(`(,dcar . ,dcdr)
(paritize-pair parity (pdomain-single-pair
(cons (p->domain parity (p-literal dcar))
(p->domain parity (p-literal dcdr))))))
(_ (if parity (pdomain-single (datum->didx datum)) pdomain-full))))
(`(type ,tag) (paritize parity (pdomain-single (tag->didx tag))))
(`(car ,p)
(paritize-pair parity (pdomain-single-pair
(cons (p->domain parity p) pdomain-full))))
(`(cdr ,p)
(paritize-pair parity (pdomain-single-pair
(cons pdomain-full (p->domain parity p)))))
(`(and ,p1 ,p2) ((if parity pdomain-meet pdomain-join)
(p->domain parity p1) (p->domain parity p2)))
(`(or ,p1 ,p2) ((if parity pdomain-join pdomain-meet)
(p->domain parity p1) (p->domain parity p2)))
(`(not ,p) (p->domain (not parity) p))
('(_) (if parity pdomain-full pdomain-empty))
(`(lookup ,_) pdomain-full)
(`(? ,_) pdomain-full)))
(define (pdomain pair symbol number nil f t)
(vector pair symbol number nil f t))
(define (pdomain-pair pd) (vector-ref pd 0))
(define (pdomain-set pd idx v)
(define pd1 (vector-copy pd))
(vector-set! pd1 idx v)
pd1)
(define (pdomain-add pd idx) (pdomain-set pd idx #t))
(define (pdomain-add-pair pd v)
(pdomain-set
pd 0 (and (not (or (equal? pdomain-empty (car v))
(equal? pdomain-empty (cdr v))))
(or (and (equal? pdomain-full (car v))
(equal? pdomain-full (cdr v)))
v))))
(define (pdomain-remove pd idx) (pdomain-set pd idx #f))
(define (pdomain-remove-pair pd) (pdomain-remove pd 0))
(define (pdomain-complement pd)
(let ((v1 (vector-map not pd)) (pr (pdomain-pair pd)))
(if (pair? pr)
(pdomain-add-pair v1 (cons (pdomain-complement (car pr))
(pdomain-complement (cdr pr))))
v1)))
(define (pdomain-meet pd1 pd2)
(let ((v1 (vector-map (lambda (a b) (not (not (and a b)))) pd1 pd2))
(pr1 (pdomain-pair pd1))
(pr2 (pdomain-pair pd2)))
(if (pdomain-pair v1)
(if (pair? pr1)
(if (pair? pr2)
(pdomain-add-pair v1 (cons (pdomain-meet (car pr1) (car pr2))
(pdomain-meet (cdr pr1) (cdr pr2))))
(pdomain-add-pair v1 pr1))
(if (pair? pr2)
(pdomain-add-pair v1 pr2)
v1))
v1)))
(define (pdomain-join pd1 pd2)
(let ((v1 (vector-map (lambda (a b) (not (not (or a b)))) pd1 pd2))
(pr1 (pdomain-pair pd1))
(pr2 (pdomain-pair pd2)))
(if (pair? pr1)
(pdomain-add-pair v1 (if (pair? pr2)
(cons (pdomain-join (car pr1) (car pr2))
(pdomain-join (cdr pr1) (cdr pr2)))
(if pr2 (cons pdomain-full pdomain-full) pr1)))
(if (pair? pr2)
(pdomain-add-pair v1 (if pr1 (cons pdomain-full pdomain-full) pr2))
v1))))
(define (pdomain-single idx) (pdomain-add pdomain-empty idx))
(define (pdomain-single-pair v) (pdomain-add-pair pdomain-empty v))
(define pdomain-empty (pdomain #f #f #f #f #f #f))
(define pdomain-full (pdomain-complement pdomain-empty))
(define tag*didx
'((pair . 0)
(symbol . 1)
(number . 2)
(() . 3)
(#f . 4)
(#t . 5)))
(define domain*-full (cons domain-full #t))
(define (pdomain->domain pd)
(let loop ((t*d tag*didx) (dmn domain-full))
(match t*d
(`((,tag . ,idx) . ,t*d)
(loop t*d (if (vector-ref pd idx) dmn
(domain-remove dmn tag))))
(_ dmn))))
(define (pdomain->domain* pd)