[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [plt-scheme] assertions and the learning of macro-fu



DrScheme does support something similar: contracts. Search in Help Desk
for more details, or see the earlier posting on this mailing list.

Robby

At Sun, 12 May 2002 22:37:50 -0400, Don Blaheta wrote:
> mzscheme seems to be lacking one construct that is key to (my style of)
> good software engineering: assertions.  (If they are in fact present,
> well, I had fun and learned a lot writing the following, so it's not
> like it's lost time. :)  I submit the following as useful to include in
> code, if not necessarily in mzscheme itself:
> 
> ;; assert: does roughly what you'd expect: evaluates its argument, and if
> ;; arg returns non-false value, continues silently.  Otherwise, abort with
> ;; assorted useful info.  If you are asserting the negation of something,
> ;; use (assert not (...)) instead of (assert (not (...))).
> ;; CAVEAT PROGRAMMOR: Currently, (assert (f x ...)) will evaluate x ... twice
> ;; in the case of assertion failure.  This does not affect program correctness,
> ;; but if x ... has side effects, error output may be incorrect.  On the other 
> ;; hand, using a side-effecty function in an assertion is probably a REALLY BAD
> ;; IDEA anyway, so I'm not too worried.
> (define-syntax (assert stx)
>   (let ([handle-assert 
>           (lambda (src-stx positive stx)
>             (let ([src (syntax-source src-stx)]
>                   [line (syntax-line src-stx)])
>               (with-syntax ([pfx (lambda (form)
>                                    (printf "Assertion failed at ~a~a: ~a~s~n"
>                                            src
>                                            (if line (format ":~a" line) "")
>                                            (if positive "" "not ")
>                                            form))]
>                             [sfx (lambda () (error "Aborting execution"))]
>                             [id-or-not (if positive (lambda (x) x) not)]
>                             [print-arg (lambda (x y)
>                                          (printf "Value of ~s is ~s~n" x y))])
>                 (syntax-case stx (assert)
>                   [(assert (a b ...))
>                    (syntax (unless (id-or-not (a b ...))
>                              (pfx '(a b ...))
>                              (let ([eargs (list b ...)])  ; b is evaled twice
>                                (for-each print-arg '(b ...) eargs))
>                              (sfx)))]
>                   [(assert a)
>                    (syntax
>                      (unless (id-or-not a) (pfx 'a) (print-arg 'a a) (sfx)))]))))])
>     (syntax-case stx (assert not)
>       [(assert not x) (handle-assert stx #f (syntax (assert x)))]
>       [(assert x)     (handle-assert stx #t (syntax (assert x)))])))
> 
> #| Example/test cases: all but the first should fail with useful info
> (assert 3)
> (assert #f)
> (assert not 3)
> (assert (zero? 3))
> (define foo 4)
> (assert (zero? foo))
> (assert (< 5 foo))
> (assert (not (zero? 0)))
> (assert not (zero? 0))
> (let ((quux 5)) (assert (zero? quux)))
> (let ((quux 0)) (assert not (< quux 4)))
> (assert (or #f #f))
> (let ((meef zero?)) (assert (meef 3)))
> |#
> 
> Getting those last four cases to all work at the same time was the
> trickiest part of this; the tradeoff I made was that arguments end up
> getting evaled twice.  That's because the thing being asserted could
> itself be a macro, so we can't use 'apply' to apply the predicate to its
> arguments; there seems to be no way to get macros (e.g. or) to work here
> with a singly-evaluated set of arguments, at least not without a great
> deal more work.  (I could be wrong, obviously. :)
> 
> Anyway, here is the output of those test cases:
> > (assert 3)
> > (assert #f)
> Assertion failed at STDIN: #f
> Value of #f is #f
> Aborting execution
> > (assert not 3)
> Assertion failed at STDIN: not 3
> Value of 3 is 3
> Aborting execution
> > (assert (zero? 3))
> Assertion failed at STDIN: (zero? 3)
> Value of 3 is 3
> Aborting execution
> > (define foo 4)
> > (assert (zero? foo))
> Assertion failed at STDIN: (zero? foo)
> Value of foo is 4
> Aborting execution
> > (assert (< 5 foo))
> Assertion failed at STDIN: (< 5 foo)
> Value of 5 is 5
> Value of foo is 4
> Aborting execution
> > (assert (not (zero? 0)))
> Assertion failed at STDIN: (not (zero? 0))
> Value of (zero? 0) is #t
> Aborting execution
> > (assert not (zero? 0))
> Assertion failed at STDIN: not (zero? 0)
> Value of 0 is 0
> Aborting execution
> > (let ((quux 5)) (assert (zero? quux)))
> Assertion failed at STDIN: (zero? quux)
> Value of quux is 5
> Aborting execution
> > (let ((quux 0)) (assert not (< quux 4)))
> Assertion failed at STDIN: not (< quux 4)
> Value of quux is 0
> Value of 4 is 4
> Aborting execution
> > (assert (or #f #f))
> Assertion failed at STDIN: (or #f #f)
> Value of #f is #f
> Value of #f is #f
> Aborting execution
> > (let ((meef zero?)) (assert (meef 3)))
> Assertion failed at STDIN: (meef 3)
> Value of 3 is 3
> Aborting execution
> 
> 
> 
> Ok, now here's the weird thing that's got me completely stumped.  If you
> paste this into a mzscheme session directly, it works fine.  If you load
> it into mzscheme, it works fine.  If you put this into a module, and you
> require it from the interpreter prompt, then positive assertions (e.g.
> (assert 3)) work fine, but negative assertions give a syntax error:
> 
> Welcome to MzScheme version 200alpha12, Copyright (c) 1995-2002 PLT
> > (require "util.sch")
> > (assert #f)
> Assertion failed at STDIN: #f
> Value of #f is #f
> Aborting execution
> > (assert not 3)
> STDIN::53: assert: bad syntax in: (assert not 3)
> 
> 
> *However*, if you require it in another module, it works fine again:
> 
> (module temp
>         mzscheme
>         (provide (all-defined))
> (require "util.sch") ; includes assert
> 
>   (define (meef x)
>     (assert x)
>     (assert not x))
> 
> ) ; end module
> 
> 
> 
> Welcome to MzScheme version 200alpha12, Copyright (c) 1995-2002 PLT
> > (require "temp2.sch")
> > (meef 3)
> Assertion failed at /Users/blahedo/274/units/temp2.sch:8: not x
> Value of x is 3
> Aborting execution
> 
> 
> So to recap, this assert macro works great *except* when it is in a
> module *and* directly required from the repl, but even then, it works in
> one syntactic form but not the other.  So obviously the macro is
> available (if I haven't required the module containing assert, then it
> just complains that assert is an unbound identifier).  Now, clearly my
> macro-fu is not at the level of some of the people on this list, but I'm
> totally baffled by this behaviour and would love not just a fix but (a
> pointer to) an explanation of why this one specific case doesn't work.
> 
> -- 
> -=-Don Blaheta-=-=-dpb@cs.brown.edu-=-=-<http://www.cs.brown.edu/~dpb/>-=-
> When your hammer is C++, everything begins to look like a thumb.
> 					--Steve Hoflich, comp.lang.c++