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
_________________________
 Racket Developers list:
 http://lists.racket-lang.org/dev

Reply via email to