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

From: Laurent (laurent.orseau at gmail.com)
Date: Sun Feb 16 14:51:41 EST 2014

In my poorly-informed point of view, I use contracts only to make checks,
so that they can be deactivated to run the code in
faster/less-error-friendly mode if needed and when I'm sufficiently
confident the program is good enough, and if the program crashes anyway,
restart it in slower/safe mode with contracts, get into exactly the same
situation and have better debug info.
(Although I have planned to do that for a long time, I've never actually
completely done it yet. Contracts could also be checked at different
boundaries just by switching a value.)
I wonder if people would consider that bad practice too btw, since it does
not seem to exist already in Racket, AFAICT.

Laurent



On Sun, Feb 16, 2014 at 1:09 AM, Matthew Butterick <mb at mbtype.com> 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?
>
> 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/20140216/068c0289/attachment.html>

Posted on the users mailing list.