What is the difference between foo/c and foo? when creating contracts?

39 views
Skip to first unread message

David Storrs

unread,
May 22, 2019, 1:02:34 PM5/22/19
to Racket Users
Is the idea that foo/c is used only when it will specify structure (e.g. (hash/c any/c symbol?)) whereas foo? is used to check that a thing has a general nature?

Alexis King

unread,
May 22, 2019, 1:22:40 PM5/22/19
to David Storrs, Racket Users
Your intuition is right, but let me make it more precise: foo? is used when something is a flat contract, and foo/c is used otherwise. Flat contracts only check first order properties of values, which is a technical term that captures what you mean by “general nature.”

The important distinction is that first order properties can be checked immediately, whereas checking of higher-order properties may need to be delayed. For example, when you write the contract

(-> integer? string?)

then you can check immediately that the value is a procedure (via the procedure? predicate), but you have to wait until the function is called to check that it is actually passed an integer. Therefore, non-flat contracts add proxying wrappers to values that do the deferred checking.

Since flat contracts only perform first-order checks, a flat contract can be used as a predicate, so you can write (foo? x) to get back a boolean. Likewise, all predicates can be used as flat contracts. This is why the foo? naming scheme is used for those contracts, since foo? is the historical naming convention in Scheme and its descendants for naming predicates. The foo/c naming convention is newer, and is specific to contracts, so it is used for contracts and contract combinators that are not simple predicates, and therefore cannot use the foo? naming convention.

Alexis

Alexis King

unread,
May 22, 2019, 1:23:06 PM5/22/19
to David Storrs, Racket Users
Your intuition is right, but let me make it more precise: foo? is used when something is a flat contract, and foo/c is used otherwise. Flat contracts only check first order properties of values, which is a technical term that captures what you mean by “general nature.”

The important distinction is that first order properties can be checked immediately, whereas checking of higher-order properties may need to be delayed. For example, when you write the contract

(-> integer? string?)

then you can check immediately that the value is a procedure (via the procedure? predicate), but you have to wait until the function is called to check that it is actually passed an integer. Therefore, non-flat contracts add proxying wrappers to values that do the deferred checking.

Since flat contracts only perform first-order checks, a flat contract can be used as a predicate, so you can write (foo? x) to get back a boolean. Likewise, all predicates can be used as flat contracts. This is why the foo? naming scheme is used for those contracts, since foo? is the historical naming convention in Scheme and its descendants for naming predicates. The foo/c naming convention is newer, and is specific to contracts, so it is used for contracts and contract combinators that are not simple predicates, and therefore cannot use the foo? naming convention.

Alexis

On May 22, 2019, at 12:06, David Storrs <david....@gmail.com> wrote:

David Storrs

unread,
May 22, 2019, 1:53:02 PM5/22/19
to Alexis King, Racket Users
Great, thanks.  I clearly need to add the section on contracts to my re-read list.  I thought I understood them, but apparently there are still bits that I'm missing.

Philip McGrath

unread,
Jun 5, 2019, 2:01:49 PM6/5/19
to David Storrs, Alexis King, Racket Users
To add a bit to what Alexis said about flat contracts:

On Wed, May 22, 2019 at 1:23 PM Alexis King <lexi....@gmail.com> wrote:
Your intuition is right, but let me make it more precise: foo? is used when something is a flat contract, and foo/c is used otherwise. Flat contracts only check first order properties of values, which is a technical term that captures what you mean by “general nature.”

The important distinction is that first order properties can be checked immediately … Since flat contracts only perform first-order checks, a flat contract can be used as a predicate, so you can write (foo? x) to get back a boolean. Likewise, all predicates can be used as flat contracts.

Some flat contracts use the foo/c naming convention when they include special error reporting, which can potentially involve a performance cost beyond a simple predicate. For example, the `xml` module provides both `xexpr?` and `xexpr/c`: https://docs.racket-lang.org/xml/index.html#(def._((lib._xml%2Fprivate%2Fxexpr-core..rkt)._xexpr%2Fc)) I write a lot of these kinds of contracts, and I think they're very effective. In my RacketCon talk last year, I discussed using contracts to do XML validation and produce error messages good enough for non-programmers to fix invalid XML files: https://youtu.be/pv0lLciMI24?t=5479

-Philip
Reply all
Reply to author
Forward
0 new messages