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.

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

Stevie
_________________________
  Racket Developers list:
  http://lists.racket-lang.org/dev

Reply via email to