[racket] contracts on liberal function inputs: avoiding duplicate effort

From: Matthias Felleisen (matthias at ccs.neu.edu)
Date: Sat Feb 15 19:27:30 EST 2014

On Feb 15, 2014, at 7:09 PM, Matthew Butterick wrote:

> Of course, these are not really contracts as we understand them. The docs for 'make-contract' say not to do this, if you read them carefully. So beware. I'm kind of curious what other people think of this application of the contract system, actually.
> 
> Why is it an abuse of the contract system to define a contract that uses a projection to change the value? 

If we were central bankers, we would keep a mathematical mode in the back of our mind to inform our decisions about real-world interest rates. 

We are PL designers and so we keep the mathematical model of topological projections in the back of our mind to inform design decisions. 

Here is the rough idea. A projection is a function that 'lowers' the value it gets. It's easiest to explain with trees (think of function as decision trees). The projection traverses the tree and prunes it at some places, usually by replacing a subtree with a leaf (bottom for functions, for example). It does NOT produce some other, unrelated tree. For 'flat' values, say numbers, the projection either passes it thru or replaces it with an error signal. That's the only kind of contract projection. 

Reality: most central bankers have a Keynesian model in mind, and even though we now pretty much know, they haven't ruined the economy completely by following it. [Well some people may think that they are on their way.]

We know by now that a projection model is too naive. But we haven't ruined Racket yet :-) (probably because we now have Christos's channel model and complete monitoring ideas in mind and intuited them for a long time.). 

-- Matthias







> 
> The docs for make-contract say "The projection must either produce the value, suitably wrapped to enforce any higher-order aspects of the contract, or signal a contract violation." Is it never true that changing a value can be the "suitable wrapping"? (Or m I overlooking some other relevant part of the docs?)
> 
> Consider this contract. It checks whether a value can be converted to a path. If so, it returns the converted value. If not, it fails. (This one's in quasicode, but I wrote a real one and it works as expected.) If this is abuse, it's very useful abuse.
> 
> (define coerce/path?
>   (make-contract
>    #:name 'coerce/path?
>    #:projection (λ (b)
>                   (λ (x)
>                     (if (can-be-path? x)
>                         (convert-to-path x)
>                         (raise-blame-error
>                          b x
>                          '(expected: "~a" given: "~e")
>                          'can-be-path? x))))))
> 
> 
> 
> 
> 
> 
> 
> 
> On Mon, Nov 18, 2013 at 10:25 AM, Ryan Culpepper <ryanc at ccs.neu.edu> wrote:
> On 11/18/2013 12:03 PM, Matthew Butterick wrote:
> In a function that permits liberal inputs, I often find that the input processing I do in a contract is duplicated at the beginning of the body of the function. Is this avoidable?
> 
> Certain functions want to be liberal with input because there are multiple common ways to represent the data. For instance, I have a function that operates on CSS RGB colors. This function should be prepared to accept these forms of input & understand that they're all the same:
> 
> "#c00"
> "#cc0000"
> '("204" "0" "0")
> #xcc0000
> '(0.8 0 0)
> 
> Let's say that my internal representation of an RGB color is described by the contract rgb-color/c:
> 
> (define rgb-color/c (list/c (real-in 0 1) (real-in 0 1) (real-in 0 1)))
> 
> But I can't use rgb-color/c as the input contract for the function because it's too narrow. So I make a second contract that tests for things that can be converted to an rgb-color:
> 
> (define (rgb-colorish? x) (or/c rgb-color/c [tests for the other input formats ...] )
> 
> To determine if the input is rgb-colorish?, this contract usually just ends up trying to convert the input to rgb-color. If it works, then the contract returns true.
> 
> But after the contract returns, I have to convert the input to an rgb-color anyhow. So I'm doing exactly the same work that the contract just finished. If the conversion is expensive, I'm doing it twice.
> 
> We usually just don't worry about the conversion happening twice. Or we do what Matthias said. Or we apply a fast approximate contract (or none at all) and then do the conversion and error checking together inside the function.
> 
> But... by abusing the contract system a little bit, though, we can get it to do the conversion for you.
> 
> Here's a "contract" combinator that takes a conversion function that returns #f to indicate failure/rejection and any other value to represent the converted result.
> 
> > (define (make-named-conversion-contract name convert)
>     (define ((proj blame) v)
>       (cond [(convert v )
>              => values]
>             [else
>              (raise-blame-error blame v
>                                 '(expected: "~a" given: "~e")
>                                 name v)]))
>     (make-contract #:name name #:projection proj))
> 
> Here's a "contract" that checks that a value is real, and if so produces its absolute value.
> 
> > (define abs/c
>     (make-named-conversion-contract
>      'abs/c
>      (lambda (v) (and (real? v) (abs v)))))
> 
> And here's a function using the converting "contract":
> 
> > (define/contract f (-> abs/c real?)
>     (lambda (x) x))
> > (f 10)
> 10
> > (f -12)        ;; <-- !!!
> 12
> > (f 'hello)
> f: contract violation
>  expected: abs/c
>  given: 'hello
>  ....
> 
> Of course, these are not really contracts as we understand them. The docs for 'make-contract' say not to do this, if you read them carefully. So beware. I'm kind of curious what other people think of this application of the contract system, actually.
> 
> Ryan
> 
> 
> ____________________
>  Racket Users list:
>  http://lists.racket-lang.org/users
> 
> ____________________
>  Racket Users list:
>  http://lists.racket-lang.org/users

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20140215/0ac8d0c7/attachment-0001.html>

Posted on the users mailing list.