[racket] Why check-expect only at the top level?

229 views
Skip to first unread message

John Riedl

unread,
Sep 5, 2011, 5:42:49 PM9/5/11
to us...@racket-lang.org
Hello Racketeers.

Summary: Why does check-expect only work at the top level?

I often find myself writing a few versions of the same function, to
show students different ways of doing things, or to experiment with
the performance of different approaches. As a trivial example,
consider:

; Check whether one number divides evenly into another.
; The modulo form is common, but the other form might be easier to understand.
; I'd like to check which one is faster. On my laptop the
divides-modulo function is nearly
; twice as fast as the divides-divide function.
(define (divides-modulo? d n) (= (modulo n d) 0))
(define (divides-divide? d n) (integer? (/ n d)))
(define (divides? d n) (divides-modulo? d n))

I'd like to test that the two divides? functions work the same by
writing a test harness that takes a divides? function and tries it out
on a bunch of values with check-expect. For instance:

(define (check-divides d?)
(check-expect (d? 4 16) true)
(check-expect (d? 4 17) false))

But when I run it I get:
. check-expect: found a test that is not at the top level in:
(check-expect (d? 4 16) true)

I've read http://docs.racket-lang.org/test-engine/index.html, which says:
"Each check form may only occur at the top-level; results are
collected and reported by the test function."

... but it doesn't say *why* this rule. Is it just to keep things
simple for students, or is there a deep reason for this limitation?

In slightly related news, I can't parse the following, in part because
of what seems to be an accidentally repeated part of the text:

(check-member-of (test any/c) (expected any/c) ...)
Accepts at least two value-producing expressions. Structurally
compares the first value to each value subsequent value specified.

Thanks,
John
_________________________________________________
For list-related administrative tasks:
http://lists.racket-lang.org/listinfo/users

Shriram Krishnamurthi

unread,
Sep 6, 2011, 7:45:04 AM9/6/11
to John Riedl, us...@racket-lang.org
I suspect everyone who teaches has run into just such a desire. But
there's a "when" problem.

We WANT students to be able to write checks before they write the
function. That means their file can look like

(check-expect (f 10) ...)
(define (f x) ...)

But if check-expect's just ran like regular code, this would no longer
work. So c-e effectively rewrites the file to

... everything else ...
... all the c-e's, wrapped in reporting code ...

(as you note in the excerpt you quote).

The problem with such a scheme is that it's not immediately clear what
to do with those inner c-e's. It's tricky to extract them to the
top-level; also, since they are under a lambda, they presumably aren't
run until the function does. But that means the expectation that all
c-e's were run has been violated.

One could imagine alternate interpretations of c-e, but the current
one is simple, consistent, and useful. One possibility is that you
want to implement some kind of "internal-check-expect" that has the
semantics you have in mind; another is that you want to move past c-e
to talking about testing and predicates, which is a bigger discussion.
An assignment like this may then be of interest:

http://www.cs.brown.edu/courses/cs019/2010/assignments/oracle

Shriram

Stephen Bloch

unread,
Sep 6, 2011, 9:00:21 AM9/6/11
to Shriram Krishnamurthi, us...@racket-lang.org, John Riedl

On Sep 6, 2011, at 7:45 AM, Shriram Krishnamurthi wrote:

> I suspect everyone who teaches has run into just such a desire.

I've asked a very similar question. And in fact, until two years ago or so check-expect DID run like normal code, which meant students had to put all their test cases AFTER (i.e. below, in the source file) their definitions. One could live with this: I told my students to write a contract comment, then write test cases, then insert a function skeleton between the comment and the test cases, then insert an inventory into the function skeleton, then fill in a whole function body in the function skeleton. But it's a little easier to explain the sequence of steps in the Design Recipe if you can just write things in the order in which you do them, so the PLT folks gave check-expect and friends this "magical" property that they run AFTER everything else at the top level.

So here's a modified version of the question. How difficult and/or confusing would it be to write check-expect in such a way that, WHEN IT APPEARS AT THE TOP LEVEL, it is magically delayed as above, but when it appears inside something else (a function, a "begin", a "local", etc.) it acts like an ordinary function?

Stephen Bloch
sbl...@adelphi.edu

Matthias Felleisen

unread,
Sep 6, 2011, 10:02:17 AM9/6/11
to John Riedl, us...@racket-lang.org

Here is a simplistic teachpack:

#lang racket/base

(require rackunit)

(provide internal-check-expect)

(define-syntax-rule
(internal-check-expect call expected)
(begin (check-equal? call expected) #true))

It is simplistic in that its error messages are probably not up to the Guillaume standards. I placed it in the same folder as a minor variation of your program:

(require "ice.rkt") ;; and required it explicitly but you could go thru the teachpack menu

(define (divides-modulo? d n) (= (modulo n d) 0))
(define (divides-divide? d n) (integer? (/ n d)))
(define (divides? d n) (divides-modulo? d n))

(define (check-divides d?)
(and (internal-check-expect (d? 4 16) true)
(internal-check-expect (d? 4 17) false)))

(check-divides divides-modulo?)
(check-divides divides-divide?)
(check-divides divides?)

This runs fine. The 'and' is needed because we need to combine expressions in a functional language. The 'true' that you see then is the result of the two.

Does this help?

Thanks for reporting the error in the teachpack docs. -- Matthias

Matthias Felleisen

unread,
Sep 6, 2011, 10:07:43 AM9/6/11
to John Riedl, us...@racket-lang.org

Here is a simplistic teachpack:

#lang racket/base

(require rackunit)

(provide internal-check-expect)

(define-syntax-rule
(internal-check-expect call expected)
(begin (check-equal? call expected) #true))

It is simplistic in that its error messages are probably not up to the Guillaume standards. I placed it in the same folder as a minor variation of your program:

(require "ice.rkt") ;; and required it explicitly but you could go thru the teachpack menu

(define (divides-modulo? d n) (= (modulo n d) 0))


(define (divides-divide? d n) (integer? (/ n d)))
(define (divides? d n) (divides-modulo? d n))

(define (check-divides d?)


(and (internal-check-expect (d? 4 16) true)
(internal-check-expect (d? 4 17) false)))

(check-divides divides-modulo?)
(check-divides divides-divide?)
(check-divides divides?)

This runs fine. The 'and' is needed because we need to combine expressions in a functional language. The 'true' that you see then is the result of the two.

Does this help?

Thanks for reporting the error in the teachpack docs. -- Matthias

On Sep 5, 2011, at 5:42 PM, John Riedl wrote:

Matthias Felleisen

unread,
Sep 6, 2011, 2:08:58 PM9/6/11
to John Riedl, us...@racket-lang.org

On Sep 5, 2011, at 5:42 PM, John Riedl wrote:

>
> In slightly related news, I can't parse the following, in part because
> of what seems to be an accidentally repeated part of the text:
>
> (check-member-of (test any/c) (expected any/c) ...)
> Accepts at least two value-producing expressions. Structurally
> compares the first value to each value subsequent value specified.


I have committed a repair for this:

(check-member-of (test any/c) (expected any/c) ...)

Checks whether the value of the test expression is structurally equal to any of the values produced by the expected expressions.
It is an error for test or any of the expected expression to produce a function value.

(check-range (test number/c) (min number/c) (max number/c))
Checks whether value of test is between the values of the min and max expressions [inclusive].

Thanks again.

Reply all
Reply to author
Forward
0 new messages