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