Hello Racketeers,
lately I've encountered a few instances where I use contracts extensively to establish both intra- and inter- module boundaries. Some of the contracts are provided for other modules to use as well. I also use scribble/srcdoc extensively, which brings the following question:
What is the best way to define, provide and document a flat contract?
Minimal example would be a contract for values that specify line weight the way like they are specified in the Unicode standard. We can have light and heavy lines. So, without naming, the contract would look like:
(or/c false? 'light 'heavy)
Assuming we want to allow the user to leave the weight unspecified with #f value.
Reading the documentation has lead me to the following contracted contract specification (let's leave aside the fact whether contracting the contract makes sense right now):
(define/contract line-weight/c
flat-contract?
(flat-named-contract
'line-weight/c
(flat-contract-predicate
(or/c false? 'light 'heavy))))
A bit heavy (self-pun intended), but it probably describes the intentions as clearly as possible. Invalid uses of contract would be caught and of course, contract violations would point directly at the offending code (assuming this is used correctly in all procedure contracts).
Now comes the tricky part. The best way to document and provide this contract using scribble/srcdoc I found is:
(proc-doc/names
line-weight/c
(-> any/c boolean?)
(v)
@{
Returns @racket[#t] if given value is valid @racket[line-style?]
weight field value.
Valid weights are:
@itemlist[
@item{@racket[#f] - no line}
@item{@racket['light] - thin (light) line}
@item{@racket['heavy] - thick (heavy) line}
]
These weight names are taken from the Unicode standard Box Drawing
block U+2500-U+257F.
})
Yes, the (-> any/c boolean?) is more suited for predicates and actually is a bit misleading for a flat-contract? values. But it makes it clear that the contract can be (and is intended) to be used as predicate too.
Digging deeper in the documentation makes me think: false? or false/c or #f - which one to choose? Probably #f, after reading the history of false/c. But many core Racket modules use false? in contracts and so it feels natural to stick with that.
Another interesting situation is where the contract and predicate versions are supposed to match compatible yet different types. I typically want the contract to match any compatible value that can be canonically converted to the particular type and the predicate should return #t if and only if the type matches exactly.
Cheers,
Dominik