This is also something I want and have thought about for some time. My main 
use case is for functions that I want to accept an arbitrary sequence, but 
coerce the sequence to a list/set/etc. if it's not already a list/set/etc. 
I think one viable approach would be to do the following:

   1. Add a notion of "coercing contracts" whose expressiveness lies 
   between chaperone contracts and impersonator contracts. A coercing contract 
   is allowed to produce a non-equal result, but only in a way that 
   "normalizes" the input: (c (c x)) should be equal to (c x) for any coercing 
   contract c, but (c x) does not have to be equal to x. Many situations that 
   currently disallow impersonator contracts could reasonably allow coercing 
   contracts, including hash/c.
   2. Make it easier to combine define/contract and module boundary 
   contracts. Using just define/contract gives you lousy blame boundaries for 
   clients of your module, but that would be trivially easy to fix if 
   `recontract-out` worked on identifiers bound with define/contract.

Using your path-string? example, this would result in something like this:

(provide (recontract-out read-config-file))
(define path-string/c (coerce/c path-string? string->path))
(define/contract (read-config-file path)
  (-> path-string/c config?)
  (parse-config (file->string path)))

On Monday, March 7, 2022 at 7:56:52 AM UTC-8 david....@gmail.com wrote:

> Would this give part of what you're looking for?
>
> #lang racket
>
> (define (my-string-length s)
>   ((or/c (and/c (or/c symbol? string?) (compose1 string-length ~a))
>          (curry raise-arguments-error 'my-string-length "invalid arg" 
> "arg"))
>    s))
>
> (my-string-length "foo")
> (my-string-length 'foo)
> (my-string-length 7)
>
>
> It's not something you'd want to write by hand in a lot of places, but it 
> seems an easy target for a macro.
>
>
> On Sun, Mar 6, 2022 at 10:47 AM Alexis King <lexi....@gmail.com> wrote:
>
>> Hello,
>>
>> As a user of the Racket contract system, I sometimes find myself thinking 
>> about the potential utility of “coercing” or “canonicalizing” contracts. In 
>> Racket programs, we often idiomatically allow values to be provided to a 
>> function in a non-canonical form for the sake of convenience. One example 
>> is the commonly-used path-string? contract, which is morally equivalent 
>> to using path? but allows the caller to omit an explicit use of 
>> string->path. Another example is the commonly-used failure-result/c 
>> contract, which allows the caller to omit wrapping non-procedures in a 
>> thunk.
>>
>> While this idiom does make life easier for one party to the contract, it 
>> ultimately just transfers the burden of canonicalizing the value to the 
>> other party. This is unfortunate, because it results in a duplication of 
>> both logic and work:
>>
>>    - 
>>    
>>    Code to canonicalize the value must be written separately and kept in 
>>    sync with the contract, which is error-prone.
>>    - 
>>    
>>    The value ends up being inspected twice: once to determine if it 
>>    satisfies the contract, and a second time to convert it to canonical form.
>>    
>> (In the nomenclature of a popular blog post I wrote a few years ago, 
>> these contracts are validating, not parsing 
>> <https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/>.)
>>
>> In theory, it is perfectly possible to implement a canonicalizing 
>> contract using the current contract system. However, such a contract has 
>> several practical downsides:
>>
>>    - 
>>    
>>    It is necessarily an impersonator contract, not a chaperone contract. 
>>    This prevents its use in places that demand a chaperone contract, such as 
>>    the *key* argument to hash/c.
>>    - 
>>    
>>    It moves actual logic into the contract itself, which means using the 
>>    uncontracted value directly is less convenient. This encourages placing 
>> the 
>>    contract boundary close to the value’s definition to create a very small 
>>    contracted region (e.g. via define/contract), even though blame is 
>>    generally more useful when the contract boundary corresponds to a 
>> boundary 
>>    between higher-level components (e.g. via contract-out).
>>    - 
>>    
>>    There is no way to write such contracts using the combinators 
>>    provided by racket/contract, so they must be implemented via the 
>>    lower level make-contract/build-contract-property API. This can be 
>>    subtle to use correctly, and it makes it unlikely that contract 
>> violations 
>>    made by the contract itself will be blamed properly according to the 
>> “indy” 
>>    blame semantics used by ->i.
>>    
>> All this is to say that the current contract system clearly discourages 
>> this use of contracts, which suggests this would be considered an abuse of 
>> the contract system. Nevertheless, the coupling between validating values 
>> and converting them to a normal form is so enormously tight that allowing 
>> them to be specified together remains incredibly compelling. I therefore 
>> have two questions:
>>
>>    1. 
>>    
>>    Has this notion of “canonicalizing” contracts been discussed before, 
>>    whether in informal discussions or in literature?
>>    2. 
>>    
>>    Is there any existing work that explores what adding such contracts 
>>    to a Racket-style, higher-order contract system in a principled fashion 
>>    might look like?
>>    
>> Thanks in advance,
>> Alexis
>>
>> -- 
>>
> 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...@googlegroups.com.
>>
> To view this discussion on the web visit 
>> https://groups.google.com/d/msgid/racket-users/CAA8dsad%2BZHLQTBK-oa5UZJfV7t33Fk0Q0L5rTG-CBnzMqH3haA%40mail.gmail.com
>>  
>> <https://groups.google.com/d/msgid/racket-users/CAA8dsad%2BZHLQTBK-oa5UZJfV7t33Fk0Q0L5rTG-CBnzMqH3haA%40mail.gmail.com?utm_medium=email&utm_source=footer>
>> .
>>
>

-- 
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.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/4537aec5-1e81-4614-b337-f49d66ee46cfn%40googlegroups.com.

Reply via email to