Re: [racket-users] Advantage of ->d over ->i

2017-12-01 Thread Philip McGrath
On Fri, Dec 1, 2017 at 10:08 PM, Ben Greenman 
wrote:

> Let me make sure I understand:
>
> 1. A converter is like a two-way function, lets say (A . <-> . B)
> 2. If someone composes two incompatible converters, like (integer? .
> <-> . symbol?) and (string? . <-> . boolean?) then they should get an
> error that points to the place where they tried to do the composing.
>
> It looks like the code above is using the contract on
> `compose-converters` to meet goal #2.
> Is that all right?
>

Yes, all of this is correct.

I will have to think about/play with your suggestion some more because I
haven't used parametric->/c before (beyond playing with it when I've
thought it might be what I was looking for and decided it wasn't).

However, I do know that at least one of the "too many things at once" that
the contract on compose-converters is doing is because of a simplification
I made for this example. In the actual code where I encountered this, I
provide only a protected interface for constructing values, so the
equivalent of:

(define/contract make-converter
  (->i ([encoded/c contract?]
[encoded->native
 (encoded/c native/c)
 (-> encoded/c native/c)]
[native/c contract?]
[native->encoded
 (encoded/c native/c)
 (-> native/c encoded/c)])
   [_ converter?])
  converter)


So I definitely agree that it shouldn't be up to the contract on
compose-converters to ensure that its arguments are valid converters; its
job should just be to ensure that they are compatible.

-Philip

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Advantage of ->d over ->i

2017-12-01 Thread Ben Greenman
Let me make sure I understand:

1. A converter is like a two-way function, lets say (A . <-> . B)
2. If someone composes two incompatible converters, like (integer? .
<-> . symbol?) and (string? . <-> . boolean?) then they should get an
error that points to the place where they tried to do the composing.

It looks like the code above is using the contract on
`compose-converters` to meet goal #2.
Is that all right?


Anyway, I think the contract for `compose-converters` should ONLY try
to make sure `compose-converters` is doing the right thing. Something
like:

```
(define/contract (compose-converters g f)
  (parametric->/c [A B C]
(-> (B . <-> . C)
(A . <-> . B)
(A . <-> . C)))
  )
```

Then the `...` should try to meet goal #2 by putting a stronger
contract on one of the converters (using something like
`define/contract`).


I don't think ->d is a good answer because I don't think a dependent
contract on "compose" is a good answer -- it's doing too many things
at once.

[[ Now I'm thinking the docs should make ->d even harder to find ...
every time I ever thought that I wanted ->d, I was better off making a
new contract combinator and using it with -> or something. ]]

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


[racket-users] Advantage of ->d over ->i

2017-12-01 Thread Philip McGrath
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
>