Skip to content

Commit f2619c5

Browse files
committed
new RFC for supporting generic interfaces
1 parent f5fca70 commit f2619c5

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
- Feature Name: Typing Generic Intefaces
2+
- Start Date: 2/23/2021
3+
- RFC PR:
4+
- Feature Commit(s):
5+
6+
# Summary
7+
8+
This RFC aims to provide almost complete support for typechecking generic interfaces.
9+
10+
# Motivation
11+
The Typed Racket type checker currently doesn't support generic interfaces,
12+
which has been one of the most wanted features.
13+
14+
15+
# Guide-level explanation
16+
There are two parts of the proposal: First, we address typing declarations of
17+
generic interfaces. Then, we show how the type checker checks structs' method
18+
definitions.
19+
20+
To annotate a generic interface, we introduce a typed counterpart for
21+
`define/generics`.
22+
23+
```racket
24+
(define-generics showable
25+
(: gen-show (showable . -> . String) )
26+
(gen-show showable)
27+
(: gen-show2 (showable showable . -> . String) )
28+
(gen-show2 some-b showable)
29+
#:defined-predicate tpred ;; (: tpred (-> showable (U 'gen-show 'gen-show2) * Boolean)). Note: No need to generate contracts
30+
#:defaults
31+
([string? (define (gen-show s) ;;
32+
(format "default string : ~a" s))
33+
(define (gen-show2 _ s)
34+
(format "default string 2: ~a" s))]
35+
[symbol? (define (gen-show s)
36+
(format "default symbol : ~a" s))
37+
(define (gen-show2 _ s)
38+
(format "default symbol 2: ~a" s))])
39+
#:fast-defaults
40+
([string? (define (gen-show s)
41+
(format "fast default string : ~a" s))
42+
(define (gen-show2 _ s)
43+
(format "fast default string 2: ~a" s))]
44+
[number? (define (gen-show n)
45+
(format "fast default number : ~a" n))
46+
(define (gen-show2 _ n)
47+
(format "fast default number 2: ~a" n))]))
48+
49+
```
50+
Apart from what Racket's `define-generics` offers, the typed
51+
`define-generics` will do the following:
52+
- introduce a generic interface type `showable`.
53+
- require users to annotate the generic functions. Note that the `formals` seems
54+
redundant, but they are necessary. This is because Racket needs a single
55+
required by-position argument to dispatch methods, and the macro would not be
56+
able to derive that argument for `formals` only from types. For example, we
57+
cannot tell which argument would act as the specializer based on the type of
58+
`gen-show2`.
59+
- generate the type for a `defined-predicate-id`. In this case, the
60+
defined-predicate-id `tpred` should have type `(-> showable (U 'gen-show
61+
'gen-show2) * Boolean)`
62+
- typecheck implemented method in `defaults` and `fast-defaults`. In each of
63+
their entries, the type of an implemented method should preserve the subtyping
64+
property for functions. Specifically, the specializer argument should be of a
65+
super type of what the preceding type predicate is true of.
66+
- typecheck methods in the `fallbacks` section. Note that since Racket does not
67+
support generic inheritance in any kind or shape, the specializer argument's
68+
type can only the generic interface type.
69+
- produce a typed immutable hash table that is assigned to `defined-table-id`
70+
when it is specified. The table's keys have a union type, `(U
71+
method-id-as-symbol ...)`, and values are simply booleans.
72+
- #:derive-property, TODO
73+
74+
For method implementation in a struct's definition, the typechecking process is
75+
also straightforward
76+
77+
```
78+
(struct
79+
point (x)
80+
#:methods gen:showable
81+
[(define/generic super-show gen-show)
82+
(define (gen-show me)
83+
(format "x is ~a" (super-show (point-x me))))
84+
(define (gen-show2 _ me)
85+
(format "x is ~a" (super-show (point-x me))))]
86+
)
87+
```
88+
89+
First, the typechecker ensures every `method-id` in a `#:methods` specification
90+
is well scoped. Then it checks if the specializer argument's and return type
91+
are covariant and if the rest are contraviant. `define/generic` makes the local
92+
id `super-show` have the same type of `gen-show`, namely `(-> showable String)`.
93+
94+
Though Racket doesn't support subclass or inheritance between generic
95+
interfaces, we can still express constraints using types in `define/generics`.
96+
97+
```
98+
(define-generics eq-able
99+
(: gen-== (eq-able eq-able . -> . Boolean) )
100+
(gen-== eq-able e)
101+
(: gen-== (eq-able eq-able . -> . Boolean) )
102+
(gen-/= eq-able e)
103+
#:fallbacks [(define/generic super-== gen-==)
104+
(define/generic super-/= gen-/=)
105+
(define (gen-== eq-able o)
106+
(not (super-/= eq-able o)))
107+
(define (gen-/= eq-able o)
108+
(not (super-== eq-able o)))])
109+
110+
(define-generics ord-able
111+
(: gen-< (Inter eq-able ord-able) (Inter eq-able ord-able) . -> . Boolean)
112+
(gen-< ord-able o)
113+
(: gen-<= (Inter eq-able ord-able) (Inter eq-able ord-able) . -> . Boolean)
114+
(gen-<= ord-able o)
115+
(: gen-> (Inter eq-able ord-able) (Inter eq-able ord-able) . -> . Boolean)
116+
(gen-> ord-able o)
117+
(: gen->= (Inter eq-able ord-able) (Inter eq-able ord-able) . -> . Boolean)
118+
(gen->= ord-able o)
119+
#:fallbacks [(define/generic super-<= gen-<=)
120+
;; (: gen-< (-> (Inter orderable eq-able) (Inter orderable eq-able) Boolean))
121+
(define (gen-< orderable o)
122+
(and (super-<= orderable o)
123+
(gen-/= orderable o)))])
124+
```
125+
126+
Here we want to implement the idea that an `ord-able` structure is also
127+
`eq-able`. Those types above enforce a stronger restraint than the untyped
128+
order-able. Because in untyped code, a struct that implements `ord-able` is not
129+
obligated to implement `eq-able`. Consider the following code:
130+
131+
```
132+
(struct dummy ([v : Any])
133+
#:methods gen:ord-able
134+
[(define (gen-< [me : Dummy] o)
135+
(and #t
136+
(gen-/= me o)))])
137+
138+
(gen-< (dummy 10) (dummy "10"))
139+
```
140+
141+
If we turn the code above into untyped code and then run it, a run-time type
142+
error will raise during the invocation of `(gen-< (dummy 10) (dummy "10"))`,
143+
because an `Dummy` instance breaks the contract of `gen-/=`.
144+
145+
However, the typechecker simply rejects the code. Since `Dummy` does not
146+
implement `gen:eq-able`, it is not of a subtype of `(Intersection eq-able ord-able)`
147+
148+
# Reference-level explanation
149+
Add a new prim for `define-generics` that supports the features mentioned in the
150+
Guide-level explanation.
151+
152+
Modify the `-struct` prim to support `#:methods`. Add code to check method
153+
implementation in pass 1 and pass 2 accordingly
154+
155+
# Drawbacks and Alternatives
156+
[drawbacks]: #drawbacks
157+
158+
## Drawbacks
159+
Adds complexity to the type checker, but it seems that the type system will be untouched.
160+
161+
# Prior art
162+
[prior-art]: #prior-art
163+
164+
As of Racket 8.0, generic interaces are not supported in Typed Racket.
165+
166+
# Unresolved questions
167+
[unresolved]: #unresolved-questions

0 commit comments

Comments
 (0)