-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.scrbl
1789 lines (1457 loc) · 60.6 KB
/
index.scrbl
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 scribble/manual
@(require "math-tools.rkt"
"bib.rkt"
(except-in scriblib/figure left)
(rename-in "model.rkt"
[render-sst-term rt]
[render-peano-term rpt])
scribble/core
(except-in pict table)
racket/date
scribble/html-properties
redex/pict)
@elem[#:style
(style #f (list (alt-tag "a")
(attributes
'((href . "https://github.com/pnwamk/sst-tutorial/")))))
@elem[#:style
(style #f
(list (alt-tag "img")
(attributes
'((style . "position: absolute; top: 0; right: 0; border: 0;")
(src . "https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png")
(alt . "Fork me on GitHub")))))]]
@title{Down and Dirty with Semantic Set-theoretic
Types (a tutorial) v0.4}
@author+email["Andrew M. Kent" "[email protected]"]
Last updated: @date->string[(current-date)]
@(table-of-contents)
@section{Introduction}
@margin-note{This is a ``living'' document: please submit bug reports
and pull requests if you spot a problem!
@url{https://github.com/pnwamk/sst-tutorial/}}
This is an informal tutorial designed to:
@itemlist[
@item{(1) briefly introduce semantic set-theoretic types, and}
@item{(2) describe in detail (i.e. with pseudo code) their
implementation details.}
]
Most of the "pseudo code" in this tutorial was generated
directly from a @emph{functioning redex model}
@~cite[bib:klein-et-al-2012] to greatly reduce the chance
for typos or other subtle bugs in their presentation.
We would like to thank Giuseppe Castagna for the time he
spent writing the wonderful, detailed manuscript @emph{
Covariance and Contravariance: a fresh look at an old issue
(a primer in advanced type systems for learning functional
programmers)} @~cite[bib:castagna-2013] which we used to
initially understand the implementation details we will
discuss, as well as Alain Frisch, Giuseppe Castagna, and
Véronique Benzaken for their detailed journal article @emph{
Semantic subtyping: Dealing set-theoretically with function,
union, intersection, and negation types}
@~cite[bib:frisch-et-al-2008] which contains thorough
mathematical and technical descriptions of semantic
subtyping. Without those works this tutorial would simply
not be possible! We @emph{highly} recommend perusing those
works as well for readers who are interested in this material.
@subsection{Prerequisites}
We assume the reader has some mathematical maturity and is
familiar with terminology commonly used by computer
scientists for describing programming languages and types.
In particular, we assume basic familiarity with:
@itemlist[
@item{how types are generally used to describe programs,}
@item{basic set-theoretic concepts,}
@item{context-free grammars (CFGs), and}
@item{defining functions via pattern matching.}
]
@subsection{Example Grammar and Function Definitions}
To be clear about how our term and function definitions
should be read we start by briefly examining a simple
well-understood domain: Peano natural numbers.
Here is a grammar for Peano naturals and booleans:
@(figure "fig:peano-grammar"
"a context-free grammar for natural numbers and booleans"
(render-peano-grammar))
This grammar states that a natural number is either @rpt[O]
(zero) or @rpt[(S p)] (i.e. the successor of some natural
number p), and that a boolean must be either @rpt[#true] or
@rpt[#false].
We can then define the function @rpt[plus] to be addition by
structural recursion on the first argument:
@(figure "fig:peano-plus"
"addition for natural numbers"
(render-mf-peano-plus))
Example usages:
@(render= peano (peano-plus (S O) O) (S O))
@(render= peano (peano-plus O (S O)) (S O))
@(render= peano (peano-plus (S (S O)) (S O)) (S (S (S O))))
@rpt[<] will be defined to be a function that returns a
@rpt[bool] indicating whether the first argument is strictly
less than the second:
@(figure "fig:peano-lt"
"less than for natural numbers"
(render-mf-peano-lt))
Example usages:
@(render= peano (peano-lt (S O) O) #false)
@(render= peano (peano-lt O (S O)) #true)
@(render= peano (peano-lt (S O) (S (S O))) #true)
@rpt[>] will be defined to be a function that returns a
@rpt[bool] indicating whether the first argument is strictly
greater-than the second:
@(figure "fig:peano-gt"
"greater than for natural numbers"
(render-mf-peano-gt))
Example usages:
@(render= peano (peano-gt (S O) O) #true)
@(render= peano (peano-gt O (S O)) #false)
@(render= peano (peano-gt (S O) (S (S O))) #false)
Things to note about these definitions (and about
definitions in general):
@itemlist[
@item{The first line is the @emph{signature} of the
function and tells us how many arguments the function takes
in addition to the types the inputs and outputs will have.
e.g., @rpt[plus] takes two natural numbers and returns a
natural number;}
@item{Underscores that appear @emph{in the signature} of a
function are simply indicators for where arguments will go
for functions with non-standard usage syntax. e.g.,
@rpt[plus] does not use underscores because we use standard
function application syntax to talk about its application
(e.g. @rpt[(plus O O)]), but @rpt[<] and @rpt[>] use
underscores to indicate they will be used as @emph{infix}
operators (i.e. we will write @rpt[(peano-lt p q)] inbetween the
two arguments instead of at the front like @rpt[plus]);}
@item{Underscores used @emph{within the definition} of the
function (e.g. the second and third clauses for @rpt[<]) are
"wild-card" patterns and will match any input;}
@item{The order of function clauses matters! i.e. when
determining which clause of a function applies to a
particular input, the function tests each clause @emph{ in
order} to see if the input matches the pattern(s);}
@item{In addition to pattern matching against the arguments
themselves, "where clauses" also act as "pattern matching
constraints" when present, e.g. @rpt[>]'s first clause
matches on any naturals @rpt[p] and @rpt[q] @emph{only
where} @rpt[(peano-lt q p)] equals @rpt[#true].}
@item{When the same non-terminal variable appears in two
places in a pattern, that pattern matches only if the same
term appears in both places. Note: this does not apply to
function @emph{signatures}, where non-terminals only
indicate what @emph{kind} of terms are accepted/returned.}
]
@bold{Important Note:} We will "overload" certain convenient
symbols during the course of our discussion since we can
always distinguish them based on the types of arguments they
are given. e.g. we will define several "union" operations,
all of which will use the standard set-theoretic symbol
@rt[t-or]. But when we see @rt[(t-or t_1 t_2)] and
@rt[(b-or b_1 b_2)], for example, we will know the former
usage of @rt[t-or] is the version explicitly defined to
operate on @rt[t]'s and the latter is the version explicitly
defined to operate on @rt[b]'s. Like the function
definitions above, @emph{all} function definitions will have
clear signatures describing the kinds of arguments they
operate on.
@section[#:tag "sec:overview"]{Set-theoretic Types: An Overview}
Set-theoretic types are a flexible and natural way for
describing sets of values, featuring intuitive "logical
combinators" in addition to traditional types for creating
detailed specifications.
@(figure "fig:types"
"set-theoretic types"
(render-nts '(("Base Types" ι) ("Types" τ σ))))
As @figure-ref["fig:types"] illustrates, languages with
set-theoretic types feature (at least some of) the following
logical type constructors:
@itemlist[
@item{@rt[(Or τ σ)] is the union of types @rt[τ] and @rt[σ],
describing values of type @rt[τ] @emph{or} of
type @rt[σ];}
@item{@rt[(And τ σ)] is the intersection of types @rt[τ] and
@rt[σ], describing values of both type @rt[τ]
@emph{and} @rt[σ];}
@item{@rt[(Not τ)] is the complement (or negation) of
type @rt[τ], describing values @emph{not} of type
@rt[τ];}
@item{@rt[Any] is the type describing all possible values; and}
@item{@rt[Empty] is the type describing no values (i.e. @rt[(Not Any)]).}]
Additionally, we may specify "specific top types", which for
each kind of atomic type denotes all values of that
particular kind:
@itemlist[
@item{@rt[Any-Pair] is the type that denotes all pairs,}
@item{@rt[Any-Fun] is the type that denotes all function, and}
@item{@rt[Any-Base] is the type that denotes all base
values (i.e. integers, strings, and booleans).}]
@(figure
"fig:specific-top-types"
"specific top type definitions"
(vc-append
15
(render≈ Any-Pair (Pair Any Any))
(render≈ Any-Fun (Fun Empty Any))
(render≈ Any-Base (Not (Or Any-Pair Any-Fun)))))
Set theoretic types frequently appear in type systems which
reason about dynamically typed languages (e.g. TypeScript,
Flow, Typed Racket, Typed Clojure, Julia), but some statically
typed languages use them as well (e.g. CDuce, Pony).
@subsection{Subtyping}
With set-theoretic types, the programmer (and the type
system) must be able to reason about how types that are not
equivalent relate. i.e., even though @rt[τ] is not the same
type as @rt[σ], is it the case that a value of type @rt[τ]
will necessarily also be a value of type @rt[σ]? In other
words, does @rt[(<: τ σ)] hold (i.e. is τ a subtype of σ)?
For example, consider the following subtyping question:
@(centered
(render-valid-judgment
(<: (Pair (Or Int Str) Str) (Or (Pair Int Str) (Pair Str Str)))))
Clearly the two types are not equal... but we can also see
that any pair whose first element is either an integer or a
string and whose second element is a string (i.e. the type
on the left-hand side) is indeed either a pair with an
integer and a string or a pair with a string and a string
(i.e. the type on the right-hand side). As a programmer then
we might reasonably expect that anywhere a
@rt[(Or (Pair Int Str) (Pair Str Str))] is expected, we
could provide a value of type @rt[(Pair (Or Int Str) Str)]
and things should work just fine.
Unfortunately, many (most?) systems that feature
set-theoretic types use sound but incomplete reasoning to
determine subtyping. This is because most type
systems reason about subtyping via syntactic inference
rules:
@(centered
(vc-append
50
(hbl-append
30
(inference-rule
(blank 5 5)
(rt (<: Empty τ)))
(inference-rule
(blank 5 5)
(rt (<: τ Any)))
(inference-rule
(blank 5 5)
(rt (<: τ τ)))
(inference-rule
(hbl-append 20 (rt (<: τ_1 τ_2)) (rt (<: σ_1 σ_2)))
(rt (<: (Pair τ_1 τ_2) (Pair σ_1 σ_2)))))
(hbl-append
30
(inference-rule
(rt (<: σ τ_1))
(rt (<: σ (Or τ_1 τ_2))))
(inference-rule
(rt (<: σ τ_2))
(rt (<: σ (Or τ_1 τ_2))))
(inference-rule
(hbl-append 20 (rt (<: τ_1 σ)) (rt (<: τ_2 σ)))
(rt (<: (Or τ_1 τ_2) σ))))))
These rules allow us to conclude the statement below the
line if we can show that the statement(s) above the line
hold. Upsides to using a system built from rules like this
include (1) the rules can often directly be translated into
efficient code and (2) we can generally examine each rule
individually and decide if the antecedants necessarily imply
the consequent (i.e. determine if the rule is valid). The
downside is that such rules are incomplete for set-theoretic
types: it is impossible to derive all valid subtyping
judgments, e.g. we cannot conclude
@rt[(Pair (Or Int Str) Str)] is a subtype of
@rt[(Or (Pair Int Str) (Pair Str Str))] even though it is
true.
For a complete treatment of subtyping for set-theoretic
types, a @emph{semantic} (instead of a syntactic) notion of
subtyping is required.
@margin-note{At the time of writing this tutorial, CDuce may
be the only example of an in-use language with a type system
which features set-theoretic types @emph{and} complete
subtyping. This is not surprising since its developers are
also the researchers that have pioneered the approaches we
will discuss.}
@subsection[#:tag "sec:semantic-subtyping"]{Semantic Subtyping}
Instead of using a syntactic approach to reason about
subtyping, we will instead us a semantic approach: types
will simply denote sets of values in the language in the
expected way.
@itemlist[
@item{@rt[True] denotes singleton set @rt[(Set #true)];}
@item{@rt[False] denotes singleton set @rt[(Set #false)];}
@item{@rt[Int] denotes the set of integers;}
@item{@rt[Str] denotes the set of strings;}
@item{@rt[(Pair τ σ)] denotes the set of pairs whose first
element is a value in @rt[τ] and whose second element is a
value in @rt[σ] (i.e. the cartesian product of @rt[τ] and @rt[σ]);}
@item{@rt[(Fun τ σ)] denotes the set of functions which can
be applied to a value in @rt[τ] and will return a value from
@rt[σ] (if they return);}
@item{@rt[(Or τ σ)] denotes the union of the sets denoted
by @rt[τ] and @rt[σ];}
@item{@rt[(And τ σ)] denotes the intersection of the sets denoted
by @rt[τ] and @rt[σ];}
@item{@rt[(Not τ)] denotes the complement of the set denoted
by @rt[τ];}
@item{@rt[Any] denotes the set of all values; and}
@item{@rt[Empty] denotes the empty set.}]
@margin-note{Our description here omits many interesting
subtleties and details about why this approach more or less
"just works"; @citet[bib:frisch-et-al-2008] @emph{
thoroughly} discuss this topic and much more and should be
consulted if the reader is so inclined.}
With our types merely denoting sets of values, subtyping can
be determined by deciding type inhabitation
(@figure-ref["fig:subtype-is-emptiness"]).
@(figure "fig:subtype-is-emptiness"
"subtyping/inhabitation equivalence"
(hbl-append
@rt[(<: τ σ)]
($ " iff ")
@rt[τ] ($ " ⊆ ") @rt[σ]
($ " iff ")
@rt[τ] ($ " \\ ") @rt[σ] ($ " = ∅")
($ " iff ")
@rt[τ] ($ " ∩ ") (inference-rule (blank 0 0) @rt[σ]) ($ " = ∅")
($ " iff ")
@rt[(And τ (Not σ))]
($ " = ∅")))
In other words, @bold{"is a particular type inhabited" is
really the only question we have to be able to answer} since
asking @rt[(<: τ σ)] is the same as asking if
@rt[(And τ (Not σ))] is uninhabited (i.e. does it denote the
empty set?).
@subsection[#:tag "sec:norm-forms"]{Deciding Inhabitation, Normal Forms}
To efficiently decide type inhabitation for set-theoretic
types we leverage some of the same strategies used to decide
boolean satisfiability:
@itemlist[
@item{types are kept in disjunctive normal form (DNF), and}
@item{special data structures are used to efficiently
represent DNF types.}]
@subsubsection{Types in Disjunctive Normal Form}
In addition to using DNF, it will be helpful to impose some
additional structure to our normal form for types. To
illustrate, first let us note that a DNF boolean formula
@($ "D"):
@(centered
(hc-append
10
($ "D = ")
(vl-append
5
(parens
($ "x" (msub "3") " ∧ ¬x" (msub "7") " ∧ x" (msub "13") " ∧ ..."))
($ "∨ "
(parens
($ "x" (msub "11") " ∧ x" (msub "4") " ∧ ¬x" (msub "1") " ∧ ¬x" (msub "2") " ∧ ...")))
($ "∨ "
(parens
($ "¬x" (msub "3") " ∧ ¬x" (msub "14") " ∧ x" (msub "1") " ∧ ...")))
($ "∨ ..."))))
can be reorganized slightly to "group" the positive and
negative atoms in each conjunction:
@(centered
(hc-append
10
($ "D = ")
(vl-append
5
(parens
(parens ($ "x" (msub "3") " ∧ x" (msub "13") " ∧ ..."))
($ " ∧ ")
(parens ($ "¬x" (msub "7") " ∧ ...")))
($ "∨ "
(parens
(parens ($ "x" (msub "11") " ∧ x" (msub "4") " ∧ ..."))
($ " ∧ ")
(parens ($ "¬x" (msub "1") " ∧ ¬x" (msub "2") " ∧ ..."))))
($ "∨ "
(parens
(parens
($ "x" (msub "1") " ∧ ..."))
($ " ∧ ")
(parens
($ "¬x" (msub "3") " ∧ ¬x" (msub "14") " ∧ ..."))))
($ "∨ ..."))))
We then observe that @${D} can be described as a set of
pairs, with one pair @${(P,N)} for each clause in the original
disjunction, where @${P} is the set of positive atoms in the
clause and @${N} is the set of negated atoms in the clause:
@(centered
(hc-append
10
($ "D = ")
(big-vee (parens
(hc-append
(parens (big-wedge ($ "x") #:below ($ "x ∈ P"))))
($ " ∧ ")
(parens (big-wedge ($ "¬x") #:below ($ "x ∈ N"))))
#:below (hbl-append ($ "(P,N) ∈ D")))))
Similarly, any type @rt[τ] can be converted into a DNF, i.e.
a set of pairs @${(P,N)}, where for each clause @${(P,N)},
@${P} contains the positive atoms (written @math{a}) which
are either a base type (@rt[ι]), a product type
(@rt[(Pair τ_1 τ_2)]), or function type
(@rt[(Fun τ_1 τ_2)]), and @${N} contains the negated atoms:
@(centered
(hc-append
10
($ "τ = ")
(big-vee
(parens
(hc-append
(parens (big-wedge ($ "a") #:below ($ "a ∈ P" )))
($ " ∧ ")
(parens (big-wedge ($ "¬a") #:below ($ "a ∈ N")))))
#:below (hbl-append ($ "(P,N) ∈ τ")))))
@subsubsection[#:tag "sec:partitioning-types"]{Partitioning Types}
In addition to being able to convert any type into DNF, for
any type @rt[τ] there exists three specialized types
@$[@rt[τ] (msup "ι")], @$[@rt[τ] (msup "×")], and
@$[@rt[τ] (msup "→")] which contain @emph{only atoms of the
same kind} such that:
@(define base-part @$[@rt[τ] (msup "ι")])
@(define pict:type-equiv-union-3-types
@$[@rt[τ] " = " "(" @rt[Any-Base] " ∧ " base-part ") ∨ ("
@rt[Any-Pair] " ∧ " @rt[τ] (msup "×") ") ∨ ("
@rt[Any-Fun] " ∧ " @rt[τ] (msup "→") ")"])
@centered[pict:type-equiv-union-3-types]
By representing a type in this way, we can efficiently
divide types into non-overlapping segments which can each
have their own DNF representation.
i.e., @base-part is a type whose atoms are all base types:
@(define pict:base-portion-DNF
(hc-append
($ base-part " = ")
(big-vee
(parens
(hc-append
(parens (big-wedge ($ "ι") #:below ($ "ι ∈ P")))
($ " ∧ ")
(parens (big-wedge ($ "¬ι") #:below ($ "ι ∈ N")))))
#:below (hbl-append ($ "(P,N) ∈ τ" (msup "ι"))))))
@centered[pict:base-portion-DNF]
@(define prod-part @$[@rt[τ] (msup "×")])
@prod-part is a DNF type whose atoms are all product types:
@(define pict:product-portion-DNF
(hc-append
($ prod-part " = ")
(big-vee
(parens
(hc-append
(parens (big-wedge @rt[(Pair τ_1 τ_2)]
#:below ($ "(" @rt[(Pair τ_1 τ_2)] ") ∈ P")))
($ " ∧ ")
(parens (big-wedge @rt[(Not (Pair τ_1 τ_2))]
#:below ($ "(" @rt[(Pair τ_1 τ_2)] ") ∈ N")))))
#:below (hbl-append ($ "(P,N) ∈ τ" (msup "×"))))))
@centered[pict:product-portion-DNF]
@(define arrow-part @$[@rt[τ] (msup "→")])
and @arrow-part is a DNF type whose atoms are all function
types:
@(define pict:function-portion-DNF
(hc-append
($ arrow-part " = ")
(big-vee
(parens
(hc-append
(parens (big-wedge @rt[(Fun τ_1 τ_2)]
#:below ($ "(" @rt[(Fun τ_1 τ_2)] ") ∈ P")))
($ " ∧ ")
(parens (big-wedge @rt[(Not (Fun τ_1 τ_2))]
#:below ($ "(" @rt[(Fun τ_1 τ_2)] ") ∈ N")))))
#:below (hbl-append ($ "(P,N) ∈ τ" (msup "→"))))))
@centered[pict:function-portion-DNF]
To illustrate what this partitioning looks like in practice,
here are a few very simple types and their equivalent
"partitioned" representation:
@(render≈
Empty
(Or (And Any-Base Empty) (Or (And Any-Pair Empty) (And Any-Fun Empty))))
@(render≈
Any
(Or (And Any-Base Any) (Or (And Any-Pair Any) (And Any-Fun Any))))
@(render≈
Int
(Or (And Any-Base Int) (Or (And Any-Pair Empty) (And Any-Fun Empty))))
@(render≈
(Pair Int Str)
(Or (And Any-Base Empty) (Or (And Any-Pair (Pair Int Str)) (And Any-Fun Empty))))
@(render≈
(Fun Int Str)
(Or (And Any-Base Empty) (Or (And Any-Pair Empty) (And Any-Fun (Fun Int Str)))))
@(render≈
(Or Int (Pair Int Str))
(Or (And Any-Base Int) (Or (And Any-Pair (Pair Int Str)) (And Any-Fun Empty))))
This technique for partitioning types into separate
non-overlapping DNFs---which will inform our strategy for
actually representing types as data structures---will make
type inhabitation inquiries easier to implement since we're
specializing our representation to describe only the
interesting, non-trivial clauses in a type. We summarize
this discussion's key takeaway in
@figure-ref["fig:type-canonical-form"] for reference.
@(figure "fig:type-canonical-form"
"canonical form for representing types"
(vl-append
20
(vl-append
5
($ "For any type " @rt[τ] " there exists specialized DNF types "
@base-part
", "
@prod-part
", and "
@arrow-part
" such that")
($ "all of the following hold:"))
pict:type-equiv-union-3-types
pict:base-portion-DNF
pict:product-portion-DNF
pict:function-portion-DNF))
@section[#:tag "sec:rep"]{Set-theoretic Type Representation}
In @secref["sec:overview"] we determined that
@itemlist[
@item{many type-related inquiries for set-theoretic types
can be reduced to deciding type inhabitation (see
@secref["sec:semantic-subtyping"]), and that because of
this}
@item{a partitioned DNF representation (summarized in
@figure-ref["fig:type-canonical-form"]) may be useful.}]
In this section we focus on the latter issue: type
representation (since how we represent types impacts how our
algorithms decide type inhabitation). We will introduce
several data structures, defining for each the binary
operators union ("∪"), intersection ("∩"), and difference
("\") and the unary operator complement ("¬").
@subsection{Types as Data Structures}
In @figure-ref["fig:type-canonical-form"] we noted a type
can be conveniently deconstructed into three partitions,
allowing us to reason separately about the base type
(@base-part), product type
(@prod-part), and function type
(@arrow-part) portion of a type:
@(centered pict:type-equiv-union-3-types)
Our representation of types will exactly mirror this structure.
@(figure "fig:type-rep"
"internal type representation"
(render-nts '(("Types (internal representation)" t s))))
As @figure-ref["fig:type-rep"] indicates, our internal
representation of types is a 3-tuple:
@itemlist[
@item{@rt[β] (the first field) is the portion which
contains base type information, corresponding to
@base-part;}
@item{@rt[Prodb] (the second field) is the portion
corresponding to product type information, corresponding to
@prod-part; and}
@item{@rt[Arrowb] (the third field) is the portion
corresponding to function type information, corresponding to
@arrow-part;}]
The specific top types used in
@figure-ref["fig:type-canonical-form"] are implicit in our
representation, i.e. we know what kind of type-information
each field is responsible for so we need not explicitly keep
around @rt[Any-Base], @rt[Any-Pair], and @rt[Any-Fun] in our
partitioned representation.
The grammar and meaning for @rt[β] will be given in
@secref["sec:base-type-rep"] and for @rt[Prodb] and
@rt[Arrowb] will be given in @secref["sec:bdd-rep"].
@subsubsection{Top and Bottom Type Representation}
The representation of the "top type" @rt[Any]---the type
that denotes all values---is written @rt[Any-t] and is
defined by placing the top @rt[β], @rt[Prodb], and
@rt[Arrowb] in each respective field
(@figure-ref["fig:top-type-rep"]).
@(figure "fig:top-type-rep"
"top type representation"
(render= sst Any-t (Type (Base - (Set)) Top Top)))
This mirrors the previous "partitioned" version of @rt[Any]
we showed earlier:
@(render≈
Any
(Or (And Any-Base Any) (Or (And Any-Pair Any) (And Any-Fun Any))))
The representation of the "bottom type" @rt[Empty]---the
type that denotes no values---is written @rt[Empty-t] and is
defined by placing the bottom @rt[β], @rt[Prodb], and
@rt[Arrowb] in each respective field
(@figure-ref["fig:bot-type-rep"]).
@(figure "fig:bot-type-rep"
"bottom type representation"
(render= sst Empty-t (Type (Base + (Set)) Bot Bot)))
Again, this mirrors the previous "partitioned" version of
@rt[Empty] we showed earlier:
@(render≈
Empty
(Or (And Any-Base Empty) (Or (And Any-Pair Empty) (And Any-Fun Empty))))
@Figure-ref["fig:specific-top-type-rep"] describes how we
represent the specific top types @rt[Any-Base],
@rt[Any-Pair], and @rt[Any-Fun] as data structures.
@figure["fig:specific-top-type-rep"
"specific top type representations"
(vc-append
15
(render= sst Any-Base-t (Type (Base - (Set)) Bot Bot))
(render= sst Any-Prod-t (Type (Base + (Set)) Top Bot))
(render= sst Any-Fun-t (Type (Base + (Set)) Bot Top)))]
@subsubsection{Type Operations}
Binary operations on types benefit from our partitioned
design: each is defined pointwise in the natural way on
each partition; type complement is defined in terms of type
difference, subtracting the negated type from the top type
(@figure-ref["fig:type-ops"]).
@(figure "fig:type-ops"
"internal type operations"
(vl-append
30
(render-mf-t-or)
(render-mf-t-and)
(render-mf-t-diff)
(render-mf-t-not)))
@subsection[#:tag "sec:base-type-rep"]{Base DNF Representation}
We now examine how a DNF type with only base type atoms can
be efficiently represented (i.e. the base portion @base-part
of a type described in
@figure-ref["fig:type-canonical-form"] and the @rt[β] field
in our representation of types described in
@figure-ref["fig:type-rep"]).
Although any type can be represented by some DNF type, in
the case of base types things can be simplified even
further! Any DNF type @base-part whose atoms are all base
types is equivalent to either
@itemlist[
@item{a union of base types
@($ "(" @rt[ι_0] " ∨ " @rt[ι_1] " ∨ ...)"), or}
@item{a negated union of base types
@($ "¬("@rt[ι_0] " ∨ " @rt[ι_1] " ∨ ...)").}]
To see why this is the case, it may be helpful to recall
that each base type is disjoint (i.e. no values inhabit more
than one base type), note that this is obviously true for
@rt[Any], @rt[Empty], and any a single base type @rt[ι] or
negated base type @rt[(Not ι)], and then examine the
definitions of base type operations presented in
@figure-ref["fig:base-type-ops"] and note how the
representation is naturally maintained.
Because any DNF of base types can be represented by a set of
base types (i.e. the elements in the union) and a polarity
(i.e. is the union negated or not), we represent the base
portion of a type @rt[β] using a tuple with these two pieces
of information (@figure-ref["fig:base-type-rep"]).
@(figure
"fig:base-type-rep"
"internal base type representation"
(render-nts '(("Base type representation" β)
("Base set polarity" ±)
("Base set" B))))
The first field is the polarity flag (either @rt[+] for a
union or @rt[-] for a negated union) and the second field
is the set of base types @rt[B] in the union.
The top base type (i.e. the type which denotes all base type
values) is a negated empty set @rt[(Base - (Set))] (i.e. it
is not the case that this type contains no base values) and
the bottom base type (the type which denotes no base type
values) is a positive empty set @rt[(Base + (Set))] (i.e. it
is the case that this type contains no base values).
@subsubsection{Base DNF Operations}
Operations on these base type representations boil down to
selecting the appropriate set-theoretic operation to combine
the sets based on the polarities
(@figure-ref["fig:base-type-ops"]).
@(figure "fig:base-type-ops"
"internal base DNF operations"
(vc-append
10
(render-mf-Base-or)
(render-mf-Base-and)
(render-mf-Base-diff)))
Base type negation is not shown (because it is not used
anywhere in this model), but would simply require "flipping"
the polarity flag (i.e. the first field in the tuple).
@subsection[#:tag "sec:bdd-rep"]{Product and Function DNFs}
In order to efficiently represent a DNF type with only
product or function type atoms (i.e. the @prod-part and
@arrow-part portions of a type described in
@figure-ref["fig:type-canonical-form"] and the @rt[Prodb]
and @rt[Arrowb] fields in our type representation described
in @figure-ref["fig:type-rep"]) we will use a binary decision
diagram (BDD).
First we include a brief review of how BDDs work, then we
discuss how they can be used effectively to represent our
product/function DNF types.
@subsubsection{Binary Decision Diagrams}
A binary decision diagram (BDD) is a tree-like data structure
which provides a convenient way to represent sets or
relations.
For example, the boolean formula
@$["(¬x∧¬y∧¬z)∨(x∧y)∨(y∧z)"] has truth table:
@(centered
(tabular
#:style 'boxed
#:sep @hspace[5]
#:column-properties '(left left left center)
#:row-properties '(bottom-border ())
(list (list @${x} @${y} @${z} @${(¬x∧¬y∧¬z)∨(x∧y)∨(y∧z)})
(list "0" "0" "0" "1")
(list "0" "0" "1" "0")
(list "0" "1" "0" "0")
(list "0" "1" "1" "1")
(list "1" "0" "0" "0")
(list "1" "0" "1" "0")
(list "1" "1" "0" "1")
(list "1" "1" "1" "1"))))
and can be represented with the following BDD:
@(centered
(draw-bdd
(list ($ "x")
(list ($ "y")
(list ($ "z")
(rt Top)
(rt Top))
(list ($ "z")
(rt Bot)
(rt Bot)))
(list ($ "y")
(list ($ "z")
(rt Top)
(rt Bot))
(list ($ "z")
(rt Bot)
(rt Top))))))
Each node in the tree contains a boolean variable. A node's
left subtree (the blue edge) describes the "residual
formula" for when that variable is true. The node's right
subtree (the red edge) describes the "residual formula" when
that variable is false. We invite the reader to compare the
truth table and corresponding BDD until they are convinced
they indeed represent the same boolean formula. It may be
useful to observe that the leaves in the BDD correspond to
the right-most column in the truth table.
@subsubsection{Types as BDDs?}
BDDs can also naturally encode set-theoretic types (in our
case, DNF product or function types). Each node has a
function/product type associated with it; henceforth we will
call this associated type the @emph{atom} of the node. A
node's left sub-tree (the blue edge) describes the "residual
type" for when the atom is included in the overall type. A
node's right sub-tree (the red edge) describes the "residual
type" for when the atom's @emph{negation} is included in
the overall type.
For example, here we have encoded the type
@rt[(Pair Int Int)]:
@(centered
(draw-bdd
(list (rt (Pair Int Int))
(rt Top)
(rt Bot))))
and here is
@rt[(Or (Pair Int Int) (Pair Str Str))]:
@(centered
(draw-bdd
(list (rt (Pair Int Int))
(rt Top)
(list (rt (Pair Str Str))
(rt Top)
(rt Bot)))))
Basically, each path in the tree represents a clause in the
overall DNF, so the overall type is the union of all the
possibly inhabited paths (i.e. paths that end in @(rt Top)).
In other words, for an arbitrary (type) BDD
@(centered
(hc-append
@rt[b] ($ " = ")
(draw-bdd
(list (rt τ)
(rt b_l)
(rt b_r)))))
we would interpret the meaning of @rt[b] (written〚@rt[b]〛)
as follows:
@(centered
(hc-append
($ "〚" @rt[b] "〛 = ")
($ "(" @rt[τ] " ∧ 〚" @rt[b_l] "〛) ∨ ("
@rt[(Not τ)] " ∧ 〚" @rt[b_r] "〛)")))
where @rt[Top] is interpreted as @rt[Any] and @rt[Bot] as
@rt[Empty].
There is, however, a well-known problem with BDDs we will
want to avoid: repeatedly unioning trees can lead to
significant increases in size. This is particularly
frustrating because---as we have previously noted---our
primary concern algorithmically is deciding type
inhabitation and taking the union of two types will have no
@emph{interesting} impact with respect to inhabitation. This
is because the union of two types is uninhabited only when
both the individual types themselves are already
uninhabited.
@subsubsection[#:tag "sec:types-as-lazy-bdds"]{Types as Lazy BDDs!}
Because there is no interesting impact on inhabitation when
computing unions, we can use "lazy" BDDs---whose unions
only fully expand when computing type intersection or
difference (i.e. operations that @emph{can} have an
interesting impact on inhabitation)---to represent our
function/product DNF types.
Nodes in lazy BDDs have---in addition to the left and right
subtrees described before---a "middle" subtree (the grey
edge) which assumes nothing about its node's atom.
In other words, for an arbitrary lazy (type) BDD
@(centered
(hc-append
@rt[b] ($ " = ")
(draw-lbdd
(list (rt τ)