[racket-users] raise-argument-error missing list?

31 views
Skip to first unread message

Kevin Forchione

unread,
Jul 5, 2019, 1:49:15 PM7/5/19
to Racket-Users List
Hi guys,
Been adding raise-argument-error to my functions to catch errors and have noticed that the 2nd version of the form doesn’t actually list the other arguments - even for the example in the docs:

>(define (feed-animals cow sheep goose cat)
(if (not (eq? goose 'goose))
(raise-argument-error 'feed-animals "'goose" 2 cow sheep goose cat)
"fed the animals"))
>(feed-animals 'cow 'sheep 'dog ‘cat)

. . feed-animals: contract violation
expected: 'goose
given: 'dog
argument position: 3rd
other arguments...:
>

Is this form recommended? I’ve noticed that the documentation says some of the error forms have been deprecated.

-Kevin

Matthew Flatt

unread,
Jul 5, 2019, 1:51:53 PM7/5/19
to Kevin Forchione, Racket-Users List
DrRacket hides the other arguments to make the error message initially
more compact. Click "..." in DrRacket to expose the arguments.
> --
> 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/91E9AD52-8F17-4BC3-98AB-5E43FFAB
> C9DF%40gmail.com.
> For more options, visit https://groups.google.com/d/optout.

Kevin Forchione

unread,
Jul 5, 2019, 2:03:45 PM7/5/19
to Matthew Flatt, Racket-Users List


> On Jul 5, 2019, at 10:51 AM, Matthew Flatt <mfl...@cs.utah.edu> wrote:
>
> DrRacket hides the other arguments to make the error message initially
> more compact. Click "..." in DrRacket to expose the arguments.
>

Thanks! The explanation has suddenly made it “intuitive” to me :)

Kevin

David Storrs

unread,
Jul 8, 2019, 11:17:26 AM7/8/19
to Kevin Forchione, Racket-Users List
Note that in many cases it can be better to use a contract as opposed to an explicit check.  For example, you could replace this:

(define (feed-animals cow sheep goose cat)
    (if (not (eq? goose 'goose))
      (raise-argument-error 'feed-animals "'goose" 2 cow sheep goose cat)
      "fed the animals"))

With this, which is precisely equivalent:

(define/contract (feed-animals cow sheep goose cat)
   (-> any/c any/c 'goose any/c any)
    "fed the animals")


Or maybe you want to verify that neither cow, sheep, nor cat are also goose:

(define/contract (feed-animals cow sheep goose cat)
   (-> (not/c 'goose) (not/c 'goose) 'goose (not/c 'goose) any)
    "fed the animals")

Or, maybe you can accept either a symbol or a string for goose:

(define/contract (feed-animals cow sheep goose cat)
   (-> any/c any/c (or/c 'goose "goose") any/c any)
    "fed the animals")

Or maybe do that case-insensitively (I'm getting rid of the excess arguments for brevity):

(define/contract (feed-animals goose)
   (-> (compose1 (curry string-ci=? "goose") ~a) any)
    "fed the animals")

Same as above, but let's insist that the return value be a non-empty string:

(define/contract (feed-animals goose)
   (-> (compose1 (curry string-ci=? "goose") ~a) non-empty-string?)
    "fed the animals")

If you really want to get nuts then you can do all kinds of inter-argument checking in a contract.  Here's an actual contract that I use in my database layer, for the 'upsert-rows' function:

(define symbol-string? (or/c symbol? string?))
(define false-or-unsupplied? (or/c false? unsupplied-arg?))

(define/contract (upsert-rows #:table             table-name
                              #:rows              rows
                              #:conflict-clause   conflict-clause
                              #:db                [conn             #f]
                              #:fields-to-update  [fields-to-update #f]
                              )
  (->i (#:table           [table symbol-string?]
        #:rows            [rows (or/c  (hash/c symbol-string? any/c)
                                       (listof (hash/c symbol-string? any/c)))]
        #:conflict-clause [conflict-clause (or/c #f 'FAIL symbol-string?)] ; 'FAIL is redundant with `symbol-string?`. It's there for clarity.
        )
       (
        #:db                 [db connection?]
        #:fields-to-update   [fields-to-update (or/c #f (non-empty-listof symbol-string?))]
        )
       ; If conflict clause is 'FAIL then fields-to-update must be #f or unsupplied          
       ; If conflict clause is #f then fields-to-update must be #f or unsupplied              
       ; If conflict clause is NOT #f then fields-to-update must be a list                    
       #:pre (conflict-clause fields-to-update)
       (or  (and (equal? 'FAIL conflict-clause)
                 (false-or-unsupplied? fields-to-update))
            (and (false? conflict-clause) (false-or-unsupplied? fields-to-update))
            (and conflict-clause (list? fields-to-update)))
       any)
...code...)





David Storrs

unread,
Jul 8, 2019, 11:18:33 AM7/8/19
to Kevin Forchione, Racket-Users List
Staircase thought:  I shouldn't have said 'precisely equivalent' since the text of the error message may differ.  'Semantically equivalent' is more correct.

Kevin Forchione

unread,
Jul 8, 2019, 1:28:34 PM7/8/19
to David Storrs, Racket-Users List
...code…)

Thanks, David! I’ll have to look into using contracts in my code. Are there any downsides to contracts over the raising an error? 

Kevin

David Storrs

unread,
Jul 8, 2019, 1:41:34 PM7/8/19
to Kevin Forchione, Racket-Users List
Nothing specific that I'm aware of, but others could answer this better.  If there are then they're probably related to speed.

Personally, I'm quite fond of them because they eliminate the need for a lot of tests and make the code much more self-documenting.

Function contracts are detailed here: https://docs.racket-lang.org/reference/function-contracts.html  In the left-hand navbar you'll see a bunch of related topics, like data-structure contracts.

Kevin Forchione

unread,
Jul 8, 2019, 1:54:04 PM7/8/19
to David Storrs, Racket-Users List


On Jul 8, 2019, at 10:41 AM, David Storrs <david....@gmail.com> wrote:

Nothing specific that I'm aware of, but others could answer this better.  If there are then they're probably related to speed.

Personally, I'm quite fond of them because they eliminate the need for a lot of tests and make the code much more self-documenting.

Function contracts are detailed here: https://docs.racket-lang.org/reference/function-contracts.html  In the left-hand navbar you'll see a bunch of related topics, like data-structure contracts.

That would be my suspicion. I suppose it’s an art form balancing the checking with the speed checks. For instance, I’ve used contracts in the provide statement, and as I’ve noted in various places in the documentation (and my own experience) they only guard the boundaries of whatever they’d are associated with. But in a call chain we can end up verifying a value repeatedly and wouldn’t it be marvelous if we could somehow pass it’s certification in syntax (no, I don’t know what I’m talking about, just dreaming) so that verification would only happen once and when needed? 

Kevin

Greg Hendershott

unread,
Jul 8, 2019, 3:56:22 PM7/8/19
to Racket-Users List
I'll chime in only because some of the usual suspects who could best
answer this might be busy with the Racket summer school this week.


I believe that function contracts can be about as fast as the sort of
checks you'd code by hand, provided that:

- The parameter contracts are simple, flat, first-order predicates like
`string?` and combinators thereof like `(or/c number? string?)` or
`(and/c number? positive?)`.

- The return value is `any` -- not even `any/c`, just `any`. Effectively
don't check the return value(s).

If callers already have contracts that will check the value as it is
used, then maybe the only thing a non-`any` return value contract would
get you is more precise blame in the error message. Whether that is
worth the probably somewhat slower speed, is your choice.


If you want the contact to protect the function itself, not merely
as-provided by a module, you can use `define/contract`.

You could also move some definitions into a sub-module, and contract the
provide from there.

In any case, the nice thing is that you can use or create any boundary
you prefer to put the contract on.

Sorawee Porncharoenwase

unread,
Jul 8, 2019, 4:43:53 PM7/8/19
to Greg Hendershott, Racket-Users List

On Mon, Jul 8, 2019 at 12:56 PM Greg Hendershott rac...@greghendershott.com wrote:

- The return value is `any` -- not even `any/c`, just `any`. Effectively
  don't check the return value(s).

If I use define/contract, does any restore tail-recursion?

You could also move some definitions into a sub-module, and contract the
provide from there.

I’m very excited about #:unprotected-submodule which could generate an unsafe submodule, and let clients pick a version they want to use. Note that it’s not in Racket 7.3, but presumably is going to be in Racket 7.4.

In general, though, I wish there are contract exporting combinators. E.g., a combinator that suppresses post-condition checking, or a combinator that approximates a higher-order contract into a first-order contract. contract-out then can generate multiple submodules with different contracts according to these combinators.

In any case, the nice thing is that you can use or create any boundary
you prefer to put the contract on.

It’s nice indeed, but I wish switching boundaries could be easier. Changing (define/contract ...) to (provide (contract-out ...)) (define ...) (and vice versa) is not fun. A macro that facilitates this switching in the stdlib would be nice.

But in a call chain we can end up verifying a value repeatedly and wouldn’t it be marvelous if we could somehow pass it’s certification in syntax (no, I don’t know what I’m talking about, just dreaming) so that verification would only happen once and when needed?

That’s what Typed Racket does. As I understand, in untyped modules, Racket also elides pair checking when it’s safe to do so.

Gustavo’s work-in-progress addition to the Chez Scheme compiler introduces the kinds of optimizations that he has long built and maintained in the current Racket implementation. For example, (f (car x) (cdr x)) can be optimized to (f (car x) (unsafe-cdr x)). These optimizations are particularly helpful for structure-field access and update, and preliminary measurements suggest that the optimizations can provide about half of the performance benefit of unsafe mode (so, typically 5-10%) without the unsafety.

But yes, eliding contract checks in general is my dream as well.




Matthias Felleisen

unread,
Jul 8, 2019, 6:01:09 PM7/8/19
to Greg Hendershott, Racket-Users List

We all are indeed at Racket school.

The arguments for/against contracts have been made over and over again especially by Betrand Meyers, before we even introduced and studied the higher-order boundary-tied variant.

+ Contracts separate the core functionality of a service module from its assumptions.
+ Contracts can be read separately as API specs ~~ no need to read the code of a function/class/method.
+ Contracts are more concise than manual checks and they are collected in a single space.
+ Contract error messages are systematically generated and readable.
+ An improvement to the contract system automatically benefits all contracts.

- Contracts might occasionally impose a small performance overhead over manual contracts. (They might also be faster if they can communicate with the compiler.)
- Contract boundaries are somewhat “stiff” as Oak says in his email. Contributions for boundary re-factorings in DrRacket are welcome.

Now you need to choose which dis/advantages you prefer — Matthias

Reply all
Reply to author
Forward
0 new messages