I recently encountered a case where a contract that (as far as I can tell)
cannot be written using ->i can be easily expressed using ->d. This
surprised me, since I thought that ->i was strictly better than ->d (given
that the docs for ->d say, "This contract is here for backwards
compatibility; any new code should use ->i instead."). Specifically, using
->d I can express a mutual dependency between the arguments of a function
(an extended example follows), whereas ->i raises an error if "generation
of 's contract depends on 's value", even if
depends on and depends on
.
I wonder if the docs should reflect that there are situations when ->d has
advantages over ->i. (Or maybe there already is a way to do this with ->i,
but the limitations seem to be inherent to its guarantees.)
For a motivating example, consider an abstraction for pairs of functions
that convert values between an "encoded" representation and a "native"
representation:
> #lang racket
> (struct converter (encoded/c encoded->native native/c native->encoded)
> #:transparent)
> (define (converter/c #:extra-encoded/c [extra-encoded/c any/c]
> #:extra-native/c [extra-native/c any/c])
> (rename-contract
>(struct/dc converter
> [encoded/c contract?]
> [encoded->native
>(encoded/c native/c)
>(-> (and/c encoded/c extra-encoded/c)
>(and/c native/c extra-native/c))]
> [native/c contract?]
> [native->encoded
>(encoded/c native/c)
>(-> (and/c native/c extra-native/c)
>(and/c encoded/c extra-encoded/c))])
>`(converter/c #:extra-encoded/c ,(contract-name extra-encoded/c)
> #:extra-native/c ,(contract-name extra-native/c
> (define/contract (converter-decode c encoded)
> (->i ([c (converter/c)]
> [encoded (c) (converter-encoded/c c)])
>[_ (c) (converter-native/c c)])
> ((converter-encoded->native c) encoded))
> (define/contract (converter-encode c native)
> (->i ([c (converter/c)]
> [native (c) (converter-native/c c)])
>[_ (c) (converter-encoded/c c)])
> ((converter-native->encoded c) native))
A nice addition would be an operation to compose converters, constructing a
converter from the encoded representation of converter A to the native
representation of converter B:
> (define unsafe-compose-converters
> (match-lambda**
> [{(converter a:encoded/c a:encoded->native a:native/c
> a:native->encoded)
> (converter b:encoded/c b:encoded->native b:native/c
> b:native->encoded)}
>(converter a:encoded/c
> (compose1 b:encoded->native a:encoded->native)
> b:native/c
> (compose1 a:native->encoded b:native->encoded))]))
The next step is to protect this operation with a contract requiring that
the two converters use a compatible intermediate representation.
Specifically, we want to insist that the encoded->native function of
converter A must always produce values satisfying the encoded/c contract of
converter B and that the native->encoded function of converter B must
always produce values satisfying the native/c contract of converter A. This
is easily done using ->d (though note that we must explicitly test that
both arguments are converters before using them in the contract, as ->d,
unlike ->i, does not guarantee this):
> (define/contract compose-converters
> (->d ([encoded-to-intermediate (converter/c #:extra-native/c
> (if (converter?
> intermediate-to-native)
> (converter-encoded/c
> intermediate-to-native)
> any/c))]
> [intermediate-to-native (converter/c #:extra-encoded/c
> (if (converter?
> encoded-to-intermediate)
> (converter-native/c
> encoded-to-intermediate)
> any/c))])
>[_ (if (and (converter? encoded-to-intermediate)
>(converter? intermediate-to-native))
> (converter/c #:extra-encoded/c (converter-encoded/c
> encoded-to-intermediate)
>#:extra-native/c (converter-native/c
> intermediate-to-native))
> none/c)])
> unsafe-compose-converters)
Now, if we compose two incompatible converters and try to use the result:
> (define converter:string->number/false
> (converter string?
> string->number
> (or/c number? #f)
> (λ (native)
>(if native
>(number->string native)
>""
> (define converter:number->number
> (converter number? add1 number? sub1))
> (converter-decode (compose-converters converter:string->number/false
>