[racket-dev] [plt] Push #24906: master branch updated

From: Ryan Culpepper (ryan at cs.utah.edu)
Date: Tue Jun 26 02:32:07 EDT 2012

On 06/25/2012 11:53 PM, Stevie Strickland wrote:
>
> On Jun 26, 2012, at 1:30 AM, Ryan Culpepper wrote:
>
>> On 06/25/2012 10:25 PM, Stevie Strickland wrote:
>>> [Hit Reply instead of Reply All, so fixing that here.]
>>>
>>> On Jun 25, 2012, at 11:53 PM, Ryan Culpepper wrote:
>>>
>>>> On 06/25/2012 09:27 PM, Stevie Strickland wrote:
>>>>> On Jun 25, 2012, at 11:21 PM, Ryan Culpepper wrote:
>>>>>
>>>>>> On 06/25/2012 09:04 PM, Asumu Takikawa wrote:
>>>>>>> On 2012-06-25 20:17:33 -0600, Ryan Culpepper wrote:
>>>>>>>> IIUC from your later message, you've implemented the generics
>>>>>>>> analogue of object/c (per-instance contract), whereas
>>>>>>>> prop:dict/contract is closer to class/c (per-type contract). It's a
>>>>>>>> little fuzzy because prop:dict/contract hacks in per-instance
>>>>>>>> contracts too in a kind of ad hoc way.
>>>>>>>
>>>>>>> That's a good point. The better analogy might be interface contracts vs.
>>>>>>> class/c. With generics, it is easy to control all points that an
>>>>>>> instance is created since constructors are just procedures. With
>>>>>>> classes, you can't get away with that since the instantiation forms are
>>>>>>> macros.
>>>>>>>
>>>>>>> The difference/advantage you might get with a per-type contract for
>>>>>>> generics is that you get a more interface-like blame story, as with
>>>>>>> interface contracts. Coverage isn't as much of an issue since you can
>>>>>>> just contract all constructors.
>>>>>>>
>>>>>>> Unfortunately, it's also not clear how to implement interface-like
>>>>>>> contracts for generics. Since the generics forms don't control the
>>>>>>> constructors, it's not obvious how to instantiate the blame at the
>>>>>>> construction site.
>>>>>>
>>>>>> You don't want to blame the construction site; the relevant
>>>>>> party is the implementation site, where the generic interface
>>>>>> is associated with concrete methods within a 'struct' form. See
>>>>>> the docs for 'struct-type-property/c' for an example.
>>>>>
>>>>> Well, there are two blame parties, right?
>>>>>
>>>>> Much like interface contracts mediate between the creator of a
>>>>> class (that implements the interface) and the client of that
>>>>> class (that instantiates objects from that interface), I would
>>>>> think the contracts for a generic interface would be between the
>>>>> creator of a specific instance (the implementation site) and the
>>>>> user of that specific instance (the constructor site).
>>>>>
>>>>> Stevie
>>>>
>>>> The analogy to interface contracts doesn't help me, because I don't
>>>> know anything about them. But I think I disagree.
>>>>
>>>> (module GEN racket
>>>>   ....
>>>>   (define-generics has-prime
>>>>     (get-a-prime has-prime))
>>>>   (provide-generics-with-contract
>>>>     (has-prime [get-a-prime (->   has-prime? prime?)])))
>>>>
>>>> (module IMPL racket
>>>>   ....
>>>>   (struct prime-box (val)
>>>>     #:methods gen:has-prime
>>>>     [(define (get-a-prime self) (prime-box-val self))])
>>>>   (provide (struct-out prime-box)))
>>>>
>>>> (module CLIENT racket
>>>>   ....
>>>>   (define p (prime-box 4))
>>>>   (get-a-prime p)) ;; ERROR
>>>>
>>>> I think IMPL should be blamed for violating the contract on gen:has-prime.
>>>>
>>>> As I see it, GEN establishes an obligation on implementors of
>>>> 'has-prime'. IMPL provides an implementation that turns out to be
>>>> faulty; it doesn't live up to the obligation imposed by GEN. CLIENT
>>>> is blameless; I don't see how the location of the constructor call
>>>> has anything to do with it.
>>>
>>> I agree that IMPL is the positive blame party here and should be
>>> blamed for not returning a prime?,
>>
>> So far so good....
>>
>>> but that's because in this example
>>> it's not protecting itself from bad prime-box values.  It shouldn't
>>> suggest that it will, in fact, return prime numbers if it isn't
>>> protecting its constructor with a contract, or checking inside its
>>> implementation of get-a-prime that it is going to return a prime
>>> number without that constructor guarantee.
>>
>> Why "but"? You have explained why IMPL is at fault. It accepted an
>> obligation and failed to meet it.
>
> The choice of "but" here was a poor one.  Replace it with "and".  I
> think we've been violently agreeing on the point that positive blame
> should be implementations, and that choice is extremely important for
> reasons below.

Okay, good.

>>> As for the negative blame, who should be responsible?  Who's
>>> responsible for the negative blame that gets triggered below, where
>>> we have a generic interface for containers mapping names to public
>>> keys?
>>>
>>> (module GEN racket
>>> ....
>>> (define-generics key-container
>>>    ([get-key (->   key-container? (->   string? prime?))]))
>>> (provide gen:key-container))
>>>
>>> (module IMPL racket
>>> ....
>>> (struct my-key-container (hash)
>>>   #:methods gen:key-container
>>>   [(define (get-key self) (lambda (str) (hash-ref my-key-container-hash str)))])
>>> (provide (struct-out my-key-generator))
>>>
>>> (module CLIENT racket
>>> ....
>>> (define gen (my-key-generator (hash '(("foo" . 5)))))
>>> (define key-lookup (get-key gen))
>>> (key-lookup 'bad-key))
>>>
>>> I'd say the person who created the generator (that is, the person who
>>> called the constructor, analogous to the object creator in the
>>> interface story).  If they wanted to pass off key-lookup to someone
>>> else without being blamed for bad  inputs, then they, of course,
>>> could just provide it with a contract.
>>
>> Let's split the CLIENT module up into three regions/labels/submodules/whatever:
>>
>> C1: (define gen (my-key-generator (hash '(("foo" . 5)))))
>> C2: (define key-lookup (get-key gen))
>> C3: (key-lookup 'bad-key))
>>
>> C2 should be blamed. The contract on get-key is (or should be)
>>
>>   (->  key-container? (->  string? prime?))
>>
>> so C2 gets back a value that it is obligated to treat as a
>>
>>   (->  string? prime?)
>>
>> but it allows that value to be misused (at point C3, but it is C2 that incurred the obligation).
>>
>> It's definitely *not* the site of the creation of the generator (C1) that should be blamed.
>
> For the most part, I agree that C2 looks like the most
> straightforward source of negative blame.  I'd just stop the email
> and say I agree, except for a problem that comes up in the following
> case:
>
>>> * What happens if you just call (get-a-prime 5) or (get-key 5)?  This
>>> seems to be a whole different blame story than someone who just
>>> creates a bad instance (like in your example), or who uses an
>>> instance badly (like in my example).  In a way, that might be akin to
>>> the following:
>>>
>>> (define/contract (f x) (->   number? number?) x)
>>> (f 3 5)
>>>
>>> Instead of being a contract error, this is just a runtime error
>>> (partially due to implementation details, but even if you erase the
>>> contract, it's still a runtime error, so that's okay).  Similarly,
>>> (get-a-prime 5) would be a runtime error without the contracts, so
>>> I'm fine with it still being a runtime error with them, and only
>>> having contracts mediate interactions that involve actual instances
>>> of a generic structure.
>>
>> No, (get-a-prime 5) is just a contract error. The contract of
>> get-a-prime is
>>
>>   (->  has-prime? prime?)
>>
>> The contract form for generic interfaces should protect both the
>> interface itself (the capability to define structs that implement
>> it) and the generic functions that it defines. (Just like the
>> provide/contract struct form protects both the constructor and the
>> accessors.)
>
> Who is the positive blame in this case?  We don't have a particular
> implementation to blame here.  The reason I chose C1, not C2, as the
> negative blame is because the existence of a particular
> implementation gives us the appropriate positive blame.  Without it,
> I don't know how to contract this appropriately.

The positive blame is GEN, because the client violated the contract of 
the *interface*, and that contract was applied by GEN.

There are only two cases when an implementation is involved as a party: 
when the implementation violates the contract (see violent agreement 
above) or when the generic module violates the contract by misusing a 
method submitted by an implementation. For example, consider this 
elaboration of GEN:

(module GEN racket
   ....
   (define-generics has-prime
     (get-a-prime has-prime))

   (define (get-two-primes hp)
     (list (get-a-prime hp "first one")
           (get-a-prime hp "second one")))

   (provide-generics-with-contract
     (has-prime [get-a-prime (-> has-prime? prime?)]))
   (provide get-two-primes))

The get-two-primes function calls get-a-prime with an additional string 
argument. The error should be GEN violated the contract on has-prime, 
with the appropriate IMPL module as the negative party. (I think.)

Ryan

Posted on the dev mailing list.