-
-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Initial support for racket/contract in TR #420
base: master
Are you sure you want to change the base?
Conversation
(Initially submitted as #417) |
Apart from the changes mentioned in #417, I don't think there's much blocking this on my end. (There's always cleanup to do, of course.) |
@btlachance My apologies for not reviewing this earlier. Do you have cycles to make it mergeable again? (if not, I'll take a look). |
@samth No worries, long ago I did say I would ping you more about this. Anyhow, I can likely take a look this week but much more likely I can take a look this weekend. So as long as making it mergeable doesn't require dramatic effort, all that should be wrapped up by this time next week. How's that sound? |
That sounds great, thanks! |
That would be so cool.
… On Dec 6, 2017, at 1:29 PM, Sam Tobin-Hochstadt ***@***.***> wrote:
That sounds great, thanks!
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.
|
fc7eb80
to
d0b686c
Compare
So, where I'm currently at (480a676) merges cleanly but there are some lingering problems that my untrained eye can see. The biggest one though is typed contract tests that error like
when I My only guess is that I have something wrong in my type reps. Apart from that I have no clue. @samth Any ideas? Edit Pushed something new that should at least compile in the Travis build. |
The reps look okay to me. I think if you added a I have no idea where |
I think @pnwamk is the best person to answer that question, but I'll also take a look today. |
d0b686c
to
480a676
Compare
The tests that fail involving mask-accessor seem to have this in common: they involve combinators defined in in typed-racket/base-env/contract-prims.rkt with a function type. But only some tests involving those combinators fail. The test with |
I'll look into this (and hopefully report back with a solution) in the next day or so. Sorry for the delay. |
(match t2 | ||
[(Con: t2-pre t2-post) | ||
(subtype-seq A | ||
(subtype* A t2-pre t1-pre obj) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
During its expansion, subtype-seq
adds an argument to the head of each operand list after the first expression (i.e. after the initial A
), so you're passing two A
values to subtype*
here (and in other places).
Take a gander at other usages of subtype-seq
in this file and you'll see they seem to mysteriously omit the first argument to subtype*
usages in the body of the subtype-seq
(because subtype-seq
provides it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't built and tested this hypothesis, but it's almost certainly what is causing the mask-accessor
issue you noticed.
The mask
error says basically it expected a type (i.e. something with a mask
struct property field -- defined here), but I got '()
(i.e. an empty A
argument in the incorrect subtype-seq
usage).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. it should read (assuming all the other details are correct):
(subtype-seq A
(subtype* t2-pre t1-pre obj)
(subtype* t1-post t2-post obj))
I'll give this PR a proper review sometime over the next couple days. |
(base-env base-types base-types-extra) | ||
(prefix-in untyped: racket/contract/base)) | ||
(provide (except-out (all-defined-out) | ||
define-contract) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be nice to not use (all-defined-out)
, and instead make define-contract
export each form, and then just manually export the other forms.
@@ -588,6 +588,24 @@ | |||
[((Distinction: _ _ S) T) | |||
(cg S T obj)] | |||
|
|||
;; XXX only barely noticed that cg takes an obj parameter | |||
;; halfway through the rebase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this "XXX ..." comment still need addressed? Removed?
[((Fun: (list (Arrow: (list S-pre) _ _ _))) | ||
(Con*: T-pre T-post)) | ||
(% cset-meet (cg T-pre S-pre obj) (cg S-pre T-post obj))] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The obj
parameter should only stay the same for recursive calls if the thing we're generating constraints for is the same.
i.e. when passing under a set-theoretic type constructor like U
, we're still talking about the same value, so obj
stays the same. However, if we're looking at some structural sub-compotent of the original thing (i.e. a field of a struct, the domain/range of a function, etc) then we're no longer looking at that same obj
and so the argument should be #f
(or just left off, since that's its default value).
I don't fully understand the Con
types yet, but my suspicion at the moment is we're no longer generating constraints for obj
when we're looking in the sub-components of a Con
(though like I said, I don't "just get" them yet).
At any rate, the conservative (i.e. sound but not complete) thing to do is leave off the obj
s in these new cases, so maybe that's best for now?
|
||
(provide-signature-elements intersect^ infer^) | ||
(provide-signature-elements intersect^ infer^ (only constraints^ meet join)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any uses of meet
or join
in a quick search of this github diff page -- do we need to provide them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup. They're used to check or/c
in check-contract.rkt.
(provide pairwise-intersect) | ||
|
||
(define (pairwise-intersect/arr s t) | ||
(match* (s t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've moved away from arr
as an abbreviation in TR's implementation due to some common ambiguities with what it stands for/implies/etc.
Would pairwise-intersect/arrow
work?
(pairwise-intersect s-rng t-rng))] | ||
[(_ _) | ||
(raise-arguments-error | ||
'pairwise-intersect/arr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int-err
raises internal TR errors (we usually use that or just omit an "else" match case.
(make-Keyword ks (pairwise-intersect ts tt) rs)] | ||
[(_ _) | ||
(raise-arguments-error | ||
'pairwise-intersect/kw |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above w.r.t. error raising
(make-NotTypeProp o (pairwise-intersect s t))] | ||
[(_ _) | ||
(raise-arguments-error | ||
'pairwise-intersect/prop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty limited set of props we support intersecting... is raising an error here the right thing? (i.e. won't this error be raised often? For example if one of the props is FalseProp
, or an And
or an Or
, etc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The errors probably aren't the right thing.
But here's some context: I initially implemented pairwise-intersect
just to get a few examples working, and I thought raising an error might help me debug the cases I missed as I added more examples.
;; Computes a lower bound of the two types w.r.t. the "precision order." The | ||
;; precision order is like the subtype order except that it does not account for | ||
;; variance. Effectively, this amounts to a fold over the two types in a uniform | ||
;; way. For base types (and, as a default, for unimplemented cases) this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a brief thing we can insert here why we want to ignore variance? Why it's sound? etc (it seems a little concerning to just read that we're ignoring variance w/o knowing why)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the comment about ignoring variance shouldn't be there. I unfortunately don't have the theory worked out really well here, but I'll try to describe why I think the remark was misleading.
(First, maybe pairwise is the wrong modifier.. but bear with me) If we use p
to refer to pairwise intersecting, then (a -> b)
p
(c -> d)
is (a
p
c) -> (b
p
d)
. I've been assuming p
is commutative, so for the domains a
p
c
should be the same as c
p
a
, and so it seems like variance shouldn't even be brought up.
[(u (Univ:)) u] | ||
[((Fun: arr1s) (Fun: arr2s)) | ||
#:when (= (length arr1s) (length arr2s)) | ||
(make-Fun (map pairwise-intersect/arr arr1s arr2s))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
arr
--> arrow
@@ -0,0 +1,121 @@ | |||
#lang racket | |||
|
|||
;; This module provides a function pairwise-intersect for calculating a lower |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file seems to raise a lot of errors instead of returning default results. Can you help me understand why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no good reason why it explicitly raises errors, so I can change it to either return default results or I can drop the catch-all cases.
Repeating what I put in one of the other comments: I initially implemented pairwise-intersect
just to get a few examples working, and I thought raising an error might help me debug the cases I missed as I added more examples.
[(o1 (Empty:)) o1] | ||
[(o o) o] | ||
[(_ _) | ||
(raise-arguments-error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same -- is an error here the right thing?
(subtype-seq A | ||
(subtype* A t2-pre t1-pre obj) | ||
(subtype* A t1-post t2-post obj))] | ||
[_ (continue<: A t1 t2 obj)])] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursive calls to subtype*
pass obj
... but are we still talking about the same obj
? If this is like recursively checking the domain and range of a function, those should not be obj
but #f
(or -empty-obj
... I forget what the default "no object" value in this file is at the moment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually you can just leave off obj
it looks like (it's an optional arg)
[(FlatCon: t2-pre t2-post) | ||
(subtype-seq A | ||
(subtype* A t2-pre t1-pre) | ||
(subtype* A t1-post t2-post))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same issues w/ subtype-seq
(i.e. erase the A
's after each subtype*
in the subtype-seq
[(Con: t2-pre t2-post) | ||
(subtype-seq A | ||
(subtype* A t2-pre t1-pre) | ||
(subtype* A t1-post t2-post))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here
[(ConFn*: t1-pre t1-post) | ||
(subtype-seq A | ||
(subtype* A t2-pre t1-pre) | ||
(subtype* A t1-post t2-post))])] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
(Con: t2-pre t2-post)) | ||
_) | ||
;; XXX hacks | ||
(match t1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XXX hacks, oh my! Um... either get rid of the hacks, or document the hacks? I don't know what this means, what the consequences are, etc.
(define (confn-out ty) (cadr (confn-type-components ty))) | ||
;; confn-type-components : Type? -> #f or ConFnInfo | ||
;; Note: only gets components for functions with a single unary arr | ||
(define (confn-type-components ty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
arr
-> arrow
;; (except-out (all-from-out racket/contract) case-> ->* ->) | ||
;; (rename-out [case-> case->/c] | ||
;; [->* ->*/c]) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this commented out code here for a reason?
Typed Racket seems to choose painfully verbose and clear in favor of cuteness and brevity in most cases. In light of that, would |
@@ -0,0 +1,156 @@ | |||
#lang scribble/manual | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read the docs - I don't really understand how the new Con
and FlatCon
types are used, when they appear, etc. They don't seem to be in any of the examples explicitly.
It would be nice if there was a description (in this PR for sure, maybe also somewhere in the implementation) of why we need |
I've reviewed and made comments. I'd still appreciate it if @samth took a detailed look at it at some point since he'll be able to better comment on some aspects. |
Thank you so much for taking a look, @pnwamk. I've been slowly making changes to address your feedback and I think the commits I just pushed to the PR address most of your remarks. I might be misunderstanding how you imagine using TR's function types/propositions. So, if you have an idea in mind then I could try to respond. But without that, here's an attempt to justify why TR's function types/propositions aren't enough:
I added some examples to the documentation that at least give a drive-by for why contracts need their own type. (I adapted the examples from the intro of the ICFP 2002 paper on higher-order contracts.) I'll respond to the inline remarks that I don't think the new commits address that well. If it helps in terms of the review process I'd be happy to respond to all of them, too. |
Typechecking or/c (in check-contract) uses infer/meet.
Still missing good examples for the binary contract types
- Updated the signature for apply-contract (context-limit is a new arg); Made the pattern names consistent with the definition in racket/contract/private/base. - Updated the type for do-partial-app (it and source-location-source appear in contract-out's expansion)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few more comments. Sorry if they're duplicates from previous discussions (I tried to search previous comments, but I could have missed some).
Also, this PR is based on some work that is summarized in a master's thesis... is that right?
Would it be useful to put a pointer to a semi-permanent URL with that thesis (e.g. https://arxiv.org/)?
[((TypeProp: o s) (TypeProp: o t)) | ||
(make-TypeProp o (pairwise-intersect s t))] | ||
[((NotTypeProp: o s) (NotTypeProp: o t)) | ||
(make-NotTypeProp o (pairwise-intersect s t))])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an invariant that ensures pairwise-intersect/prop
isn't called with a TypeProp
and a NotTypeProp
? Or that it's never called with a LeqProp
and some other prop?
I ask because there's no case for handling these combinations. If those combinations arise... is that actually an internal error? Or... does that mean we need to return some conservative, sound default result?
[((Fun: arr1s) (Fun: arr2s)) | ||
#:when (= (length arr1s) (length arr2s)) | ||
(make-Fun (map pairwise-intersect/arrow arr1s arr2s))] | ||
[((Result: ss pset-s o1) (Result: ts pset-t o2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't help but be a little skeptical this is the right thing to do for functions in general (although I have not spent a lot of time thinking about this issue specifically, so maybe there's some obvious things I'm missing).
Why do two functions need the same number of arrows to be pairwise intersected? And why are we pairwise intersecting their arrows only in a pointwise fashion?
If this is right, maybe a comment as to why would be helpful for posterity's sake? If we're less confident, perhaps for now only support pairwise intersecting functions with a single arrow? Just a thought.
dbound)] | ||
[((Poly: vs b) _) | ||
#:when (or (subtype s t) (subtype t s)) | ||
s] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's neat that we can just check subtyping here. That makes me wonder, should pairwise-intersect
start out with a subtype/subval/implies-atomic?/etc check up front before beginning to examine and recur into the specific types/propositions?
See #833 as well. |
This pull request implements initial support for using racket/contract in TR.
Type system from a high-level
Contracts in TR have two type arguments and are written as
(Con S T)
.The first argument is the contract's input type, which specifies what it can
monitor. For example, using the function
positive?
as a contract would haveinput type Real. You can think of the input type like the function's domain.
The second argument is the contract's output type, which indicates extra
information we can conclude if the contract does not fail. For
positive?
, itsoutput type is
Positive-Real
. As you may have guessed, this information comesfrom the function type's propositions.
The entire contract type of
positive?
is(Con Real Positive-Real)
.A function contract like
(->/c positive? positive?)
has a slightly morecomplicated type. By the domain contract's output type, we know the function
contract will only allow
Positive-Real
s to get through to the function.Because the range contract can only monitor
Real
s, we know the function mustproduce a
Real
. Thus, we say that the input type of the function contract is(-> Positive-Real Real)
. We construct the output type of the function contractwith similar reasoning, and get that the entire type of the function contract is
(Con (-> Positive-Real Real) (-> Real Positive-Real))
.What's supported?
I mostly focused on the combinators found in the "Data Structure Contracts"
section of the docs, the function contract combinators
->
and->i
,provide/contract
, andcontract-out
.Notable missing forms:
->*
unsupplied-arg?
andthe-unsupplied-arg
from->i
struct/c
any
range contractBrief implementation details
Typed contract forms are provided by contract-prims.rkt (a name that is
annoyingly close to prims-contract.rkt). Most forms can be directly given a
type, e.g.
>/c
can be given a function type.Other combinator forms are a little more complicated. TR provides its own
version of these that annotate the surface syntax before delegating to the
racket/contract's version of the form. After expansion, TR recovers the
annotated pieces, typechecks those pieces, and uses that to calculate the type
of the contract.
One particularly tricky part of the implementatation is that contract-out and
provide/contract generate transformers for the provided, contracted, bindings.
We want typed code with contracts on it to be able to go to untyped code.
However, macros defined in TR are generally forbidden from doing so. These
bindings are special-cased in tc-toplevel. I don't know what implications this
has on TR's invariants.
Any sort of feedback is welcome---type system, implementation, comment prefix
syntax, etc.