Precondition asserts in macros

484 views
Skip to first unread message

Shantanu Kumar

unread,
Mar 20, 2012, 8:34:01 AM3/20/12
to Clojure
Hi,

The way preconditions are invoked in Clojure 1.3.0 seems to have
changed since Clojure 1.2:

(defmacro foo
[bar & body]
{:pre [(string? bar)]}
...)

(foo "bar34" ...) ; doesn't complain, which is OK
(foo (str "baz" 34) ...) ; Error! (I wanted this to pass)

When I write the precondition like this:

{:pre [`(string? ~bar)]}

It doesn't seem to check the precondition at all in 1.3.0.

Can somebody suggest me what am I missing? I want both the examples
above to be verified and passed as OK.

Shantanu

Chas Emerick

unread,
Mar 20, 2012, 9:07:45 AM3/20/12
to clo...@googlegroups.com
Your second `foo` call fails in 1.2 as well. If there was ever a time when it would have succeeded, it would have been a bug. Since `foo` is a macro, it receives its arguments unevaluated, so `(str "baz" 34)` will always be received as a list of three values.

The syntax-quote precondition simply expands into a list containing the symbol 'clojure.core/string? and the value of bar; this is a logically-true value, and so does not trigger any error.

You want something like:

(defmacro foo
[bar & body]

`(let [bar# ~bar]
(when-not (string? bar#) (IllegalArgumentException. "msg"))
...))

- Chas

> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Daniel Solano Gomez

unread,
Mar 20, 2012, 9:18:08 AM3/20/12
to clo...@googlegroups.com
Hello,

I am not sure that anything has changed between Clojure 1.2.1 and
Clojure 1.3.0 with respect to pre- and post-conditions. However, I
think I have any idea as to what may be going on.

On Tue Mar 20 05:34 2012, Shantanu Kumar wrote:
> Hi,
>
> The way preconditions are invoked in Clojure 1.3.0 seems to have
> changed since Clojure 1.2:
>
> (defmacro foo
> [bar & body]
> {:pre [(string? bar)]}
> ...)
>
> (foo "bar34" ...) ; doesn't complain, which is OK
> (foo (str "baz" 34) ...) ; Error! (I wanted this to pass)

As I understand it, as foo is a macro, bar isn't being evaluated in the
precondition. As a result it just gets whatever bar is. In the first
example, bar is "bar34", but in the second example, it's the list (str
"baz" 34). So, if you changed the precondition to (list? bar), the
first would fail, and the second would succeed.

> When I write the precondition like this:
>
> {:pre [`(string? ~bar)]}
>
> It doesn't seem to check the precondition at all in 1.3.0.

In this case, your precondition is evaluating to something like:

(clojure.core/seq
(clojure.core/concat
(clojure.core/list (quote clojure.core/string?))
(clojure.core/list bar)))

And this does not evaluate to false. As such, the precondition is being
checked, it's just not checking what you want it to check.


> Can somebody suggest me what am I missing? I want both the examples
> above to be verified and passed as OK.

I think what you may want is something like:

{:pre [(string? (eval bar))]}

However, I must question whether or not you really want to be doing
this. The precondition is being evaluated a compile/macro-expansion
time, not run time. As such, you should probably only use pre- and
post-conditions on defmacro if they are checking the arguments to the
macro itself.

Just some thoughts.

Sincerely,

Daniel

signature.asc

Justin Kramer

unread,
Mar 20, 2012, 9:19:29 AM3/20/12
to clo...@googlegroups.com
Another option: create a helper function to do the work and have the macro call that:

(defn foo* [bar body-thunk]
  {:pre [(string? bar)]}
  (body-thunk)) ;or whatever

(defmacro foo [bar & body]
  `(foo* ~bar (fn [] ~@body))

Justin

Bronsa

unread,
Mar 20, 2012, 9:29:38 AM3/20/12
to clo...@googlegroups.com

try with {:pre [(string? (eval bar))]}

Chas Emerick

unread,
Mar 20, 2012, 10:29:48 AM3/20/12
to clo...@googlegroups.com
Using an explicit eval is generally not a good idea, as `bar` will be evaluated at least twice: once in validating the precondition, and again in the macroexpansion. This can lead to all sorts of "interesting" problems if `bar` happens to be a side-effecting expression.

- Chas

Shantanu Kumar

unread,
Mar 20, 2012, 12:19:34 PM3/20/12
to Clojure
Thanks everybody for the pointers! I realized I was wrong about the
behavior being different in 1.2; rather it is the same since 1.1.0.

Shantanu
Reply all
Reply to author
Forward
0 new messages