Skip to content

Commit 3b5e871

Browse files
committed
RFC for initial implementation of types for untyped contracts
1 parent ee2a21f commit 3b5e871

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
- Feature Name: basic-typed-contracts
2+
- Start Date: 2019-06-30
3+
- RFC PR: (leave this empty)
4+
- Feature Commit(s): (leave this empty)
5+
6+
# Summary
7+
8+
Give TR typed analogues of the `racket/contract` module. This needs a
9+
new type constructor, `Contract`, to support higher-order
10+
contracts. And adding a `FlatContract` type constructor helps e.g. to
11+
give a more precise type to combinators like `not/c`.
12+
13+
(The MS thesis related to this work is on my personal page at Tufts
14+
<https://www.eecs.tufts.edu/~blachanc/ms-thesis.pdf>. I don't yet have
15+
an arXiv link because I need to get my Scribble build working enough
16+
to at least get the intermediate TeX out.
17+
18+
Any mention of "the current implementation" refers to the pull request
19+
at <https://github.com/racket/typed-racket/pull/420>.)
20+
21+
# Motivation
22+
23+
Racket programmers use the contract forms provided by
24+
`racket/contract` but there currently isn't a clear path for Typed
25+
Racket programmers to do the same. Without those forms, enforcing
26+
invariants outside of the type system's sweet spot is harder, and
27+
migrating contract-using Racket programs to Typed Racket is harder.
28+
29+
The current implementation supports most of the data structure
30+
contracts in the contract documentation, and it also supports most of
31+
`->i`, `->*`, and `contract-out`.
32+
33+
# Guide-level explanation
34+
35+
## Basics/contracts over first-order types
36+
37+
(Assumes a passing knowledge of Racket's contract library and TR's
38+
propositions. In typed contracts, I write `->/c` for Racket's `->`
39+
contract combinator.)
40+
41+
Racket's contract system monitors how values flow through
42+
contracts. Using those same contracts in Typed Racket means we'll need
43+
to give typed values to the contract system, and so the type system
44+
will have to ensure that those values are used appropriately.
45+
46+
We can use typed functions as contracts. To properly use a typed
47+
function, the type system has to ensure that the contract is only ever
48+
used to monitor values that agree with the function's type. Being a
49+
predicate on `Integer`s, we can use `even?` to monitor integers:
50+
51+
`(contract even? -6 'pos 'neg)`
52+
`(contract even? 6 'pos 'neg)`
53+
54+
We can also combine typed functions using contract combinators like
55+
`and/c`:
56+
57+
`(contract (and/c even? positive?) 10 'pos 'neg)`
58+
59+
When we combine contracts, we need to be careful that the combination
60+
makes sense. But first we need to talk about the types of the
61+
contracts *being* combined. We've already seen that we need to make
62+
sure a function is only used as a contract to monitor values of the
63+
right type. But one idiom in Racket is to write contracts like `(and/c
64+
exact-integer? even?)`: Here, a (Racket) contract programmer uses the
65+
`exact-integer?` test to guard that `even?` in their contract is only
66+
applied to integers. Without the guard, `even?` might raise a runtime
67+
error (its own contract violation) unrelated to the contract the
68+
programmer wrote. To give types to these contracts, then, we need to
69+
know that whatever comes out of the `exact-integer?` contract is safe
70+
to flow into the `even?` contract. So in addition to needing to know
71+
the types of values a contract can be used on, we also need to know
72+
that what comes out of a contract is safe for some subsequent
73+
contract, like in `and/c`. Typed Racket's propositions help us uncover
74+
this last part.
75+
76+
The two parts of a contract's type explain what the contract can be
77+
used on and what comes out of the contract. For `exact-integer?`, its
78+
function type says it can be used on `Any` and that it has a positive
79+
proposition for `Integer`, thus it has contract type `(Contract Any
80+
Integer)`. The type of `even?` is `(-> Integer Boolean)`, with no
81+
propositions, and so it has contract type `(Contract Integer
82+
Integer)`. We say that the first part of a contract's type is its
83+
*input type* and that the second part of a contract's type is its
84+
*output type*. For example, the input type of `exact-integer?` is
85+
`Any` and its output type is `Integer`.
86+
87+
Back to type checking the combined contract, `(and/c exact-integer?
88+
even?)`. From the types above, we know that what comes out of
89+
`exact-integer?` can safely go in to `even?`. And since everything
90+
lines up, we take the the input type of the first contract type
91+
(`Integer`) and the output type of the last contract (`Integer`) and
92+
give the whole contract type `(Contract Any Integer)`.
93+
94+
## Contracts on higher-order types
95+
96+
If a function only consumes and produces positive reals, a Racket
97+
programmer might express that information using the contract `(->/c
98+
positive? positive?)`. In Typed Racket, that same information could be
99+
expressed with the type `(-> Positive-Real Positive-Real)`. So
100+
although that exact contract might not make sense in a Typed Racket
101+
program, we still want to give it a type---the path from Racket to
102+
Typed Racket isn't always smooth so we might be able to help the
103+
Racket programmer if we give a meaningful type to that contract.
104+
105+
Starting with the function `positive?`, its type is `(-> Real
106+
Boolean)` and it has a positive proposition for `Positive-Real`. So
107+
based on what we saw in the previous section, it has the contract type
108+
`(Contract Real Positive-Real)`.
109+
110+
The function contract combinator `->/c` wraps whatever function it is
111+
applied to, ensuring that a caller provides arguments that satisfy the
112+
domain contract and that the function returns a result that satisfies
113+
the range contract; Values flow from the caller through the domain
114+
contract into the function, and out of the function and through the
115+
range contract.
116+
117+
This value flow is reflected in the type of a function contract. For
118+
our `(->/c positive? positive?)` the type is
119+
120+
(Contract (-> {2} Positive-Real Real {3})
121+
(-> {1} Real Positive-Real {4}))
122+
123+
In the output type of the contract, here a function type, the domain
124+
type is `Real` (marked `{1}`). This is because after applying the
125+
contract to a function, calling the result with a value will apply the
126+
domain contract (with input type `Real`) to that value. If the value
127+
passes the domain contract, we know it is a `Positive-Real` by the
128+
domain contract's output type, which is reflected in the input type's
129+
domain contract (marked `{2}`). This means that a function we apply
130+
this contract to has to accept `Positive-Real` values.
131+
132+
In the input type of the contract, the range of the type is `Real`
133+
(marked `{3}`) because applying the contract to a function is only
134+
allowed if that function returns a value that can flow through the
135+
range contract (whose input type is `Real`). Finally, in the output
136+
type of the contract the range type is `Positive-Real` (marked `{4}`)
137+
because that's the output type of the range contract. This means that
138+
if we apply this contract to a function, then the resuling function
139+
returns at least a `Positive-Real` if it returns at all.
140+
141+
# Reference-level explanation
142+
143+
## Interaction with other features
144+
145+
As far as I can tell contracts are mostly self-contained. Some
146+
exceptions:
147+
148+
- Predicates are subtypes of the `FlatContract` type.
149+
150+
- Values of `FlatContract` type are not functions (`FlatContract`
151+
currently corresponds to Racket's `flat-contract?` which is `#t`
152+
for e.g. numbers)
153+
154+
- Applying a function contract (with the current type rule for
155+
contract application) combines the range type of the function
156+
contract's output type with the range type of the function that
157+
will be monitored. In the code this is called "pairwise
158+
intersection" and in the thesis this is the `comb` operator.
159+
160+
As far as subtyping goes, `(Contract S T)` and `(FlatContract S T)` is
161+
contravariant in `S` and covariant in `T`.
162+
163+
## How the feature would be implemented
164+
165+
The MS thesis linked up top gives type rules formalizing what I walked
166+
through in the guide-level explanation. It also discuss some of the
167+
implementation techniques used.
168+
169+
(The current implementation may have some support for this part of the
170+
RFC, too.)
171+
172+
## Corner cases
173+
174+
Because I haven't worked out how to compile a contract type to a
175+
contract for typed/untyped interaction, typed code can't let untyped
176+
code use a value of contract type. At the moment I believe the current
177+
implementation raises an error (maybe an internal one?) when providing
178+
a value of contract type because I haven't defined a way to compile
179+
those types to contracts.
180+
181+
I haven't properly looked at how these contract types interact with
182+
polymorphic types. Currently the only way to get primitive contracts
183+
with such a type are a predicates on values of some polymorphic type,
184+
e.g. a function like `null?`.
185+
186+
# Drawbacks and Alternatives
187+
188+
[drawbacks]: #drawbacks
189+
190+
I don't see drawbacks in adding typed contracts themselves to Typed
191+
Racket; I see a potential drawback to the type system I've proposed.
192+
193+
The rule for contract application is complicated by the two-argument
194+
`Contract` type, and it may not be worth the cognitive overhead. One
195+
reason for the two type arguments is so that the type computed by the
196+
`and/c` rule is not far from what you get from a sequence of contract
197+
applications (i.e. the `and/c` rule is privileged, but with the two
198+
type arguments it's not as privileged). But this may not be worth the
199+
complexity.
200+
201+
Using a unary type constructor would remove the whole notion of input
202+
and output types, which could make for an easier type rule for the
203+
`and/c` combinator and for the other combinators, too.
204+
205+
# Prior art
206+
[prior-art]: #prior-art
207+
208+
I haven't seen a language that combines typed, higher-order contracts
209+
and subtyping---the combination seems to be unique to bringing
210+
Racket's contracts to Typed Racket. The current design is written up
211+
in the MS thesis linked up top.
212+
213+
The original higher-oder contract paper [ff-icfp2002] uses contract
214+
types in a simply typed language, and that type system was also used
215+
in some work around higher-oder assertions and Haskell
216+
[hjl-flp2006]. The Haskell work supports polymorphism but doesn't have
217+
subtyping.
218+
219+
- [ff-icfp2002] Robert Bruce Findler and Matthias Felleisen. Contracts
220+
for Higher-Order Functions. ICFP 2002
221+
- [hjl-flp2006] Ralf Hinze, Johan Jeuring, and Andres Löh. Typed
222+
Contracts for Functional Programming. Functional and Logic
223+
Programming, 2006.
224+
225+
# Unresolved questions
226+
[unresolved]: #unresolved-questions
227+
228+
> What parts of the design do you expect to resolve through the RFC
229+
> process before this gets merged?
230+
231+
Whether the two-argument `Contract` type constructor and the
232+
corresponding contract application rule is too complex
233+
234+
> What parts of the design do you expect to resolve through the
235+
> implementation of this feature before stabilization?
236+
237+
Giving better error messages, simplifying tricky parts of the
238+
implementation (assuming that's in scope for "parts of the design";
239+
e.g. recovering the dependency identifiers in a ->i contract from a
240+
fully expanded program)
241+
242+
> What related issues do you consider out of scope for this RFC that
243+
> could be addressed in the future independently of the solution that
244+
> comes out of this RFC?
245+
246+
- Types for object contracts, unit contracts, parametric contracts
247+
(i.e. `racket/contract/parametric`)
248+
249+
- Typed/untyped interaction at contract types

0 commit comments

Comments
 (0)