|
| 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