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+unsubscr...@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.

Reply via email to