Clojure 1.3 "wonky" behavior

287 views
Skip to first unread message

Micah Martin

unread,
Oct 20, 2011, 1:53:48 PM10/20/11
to clo...@googlegroups.com
I recently tried to get Speclj running on Clojure 1.3 and came across the following problem:

(list
(declare ^:dynamic p)
(defn q [] @p))

(binding [p (atom 10)]
(q))

> java.lang.ClassCastException: clojure.lang.Var$Unbound cannot be cast to clojure.lang.IDeref

Thanks to @cemerick for helping me condense the snippet, and thanks to both @cemerick and @chouser for the lively discussion on IRC. Yet the discussion was inconclusive. Is the above expected behavior?

Micah

Meikel Brandmeyer

unread,
Oct 20, 2011, 3:09:23 PM10/20/11
to clo...@googlegroups.com
Hi,

I'm not sure. Maybe it's undefined? Clojure compiles per top-level form. In this case this is the call to list. However in p does not exist before. It is created by the def inside this form. And maybe I'm just a weenie, but I wouldn't bet on some not very well documented peculiarities of def.

From the “one top-level form” rule do is an exception. And indeed this seems to be for a reason:

user=> (do (declare ^:dynamic p) (defn q [] @p))
#'user/q
user=> (binding [p (atom 10)] (q))
10

So maybe the rule of thumb is: never reference defs made in the same top-level form unless this form is a do.

I'm sorry. I can't explain things further. I suspect that the effect you see is due to some accidental, implementation-dependent behavior of the compiler.

Sincerely
Meikel

Chris Perkins

unread,
Oct 20, 2011, 4:15:32 PM10/20/11
to clo...@googlegroups.com
To put it another way:  a top-level "do" is special-cased to compile and then immediately execute each contained form, in order. Any other top-level form, such as "list", will be fully compiled, including all contained forms, and only then executed. In this case, the defn cannot compile because the preceding declare has not yet executed - it has only compiled.

- Chris

Chris Perkins

unread,
Oct 20, 2011, 4:31:59 PM10/20/11
to clo...@googlegroups.com
Note: I forgot to preface that with "I think..." :)  Upon experimenting briefly, it turns out I was wrong about how Clojure works (that seems to happen a lot with me).  A declare/def defines a var even when it's not executed!

user> (defn xxx [] (declare yyy))
#'user/xxx
user> yyy
#<Unbound Unbound: #'user/yyy>

Well, I learned something today.

- Chris

Chouser

unread,
Oct 20, 2011, 8:55:43 PM10/20/11
to clo...@googlegroups.com

But it only interns the Var, it doesn't fully set it up. Particularly
relevant to the OP's example is that the metadata from the name symbol
is not transferred to the Var (and the changes to the Var based on
:dynamic are not applied) until runtime for the 'def', even though the
Var exists at compile time.

Here's a macro that expands at compile time to the *compile* time
metadata of the var named in its argument:

(defmacro compile-time-meta [x] (meta (resolve x)))

Now observe how it behaves differently than a runtime call to 'meta':

(vector
(declare ^:dynamic *myvar*)
(meta #'*myvar*)
(compile-time-meta #'*myvar*)))

The above returns:

[#'user/*myvar*
{:ns #<Namespace user>, :name *myvar*, :dynamic true, :declared true, ...}
{:ns #<Namespace user>, :name #<Unbound Unbound: #'user/*myvar*>}]

First is the Var itself.
Next is the metadata of the Var at runtime, after the entire form has
been compiled and therefore the metadata from the name has been
applied to the Var, including the :dynamic flag.
Finally we see that when our macro was expanded the Var existed but
had minimal metadata. This was after the declare was compiled but
before any part of the 'vector' form was run. There is no :dynamic
flag, and anything that depends on that flag at compile time to work
correctly (such as a function that refers the for Var) will fail to
work correctly.

--Chouser

Micah Martin

unread,
Oct 24, 2011, 12:44:55 PM10/24/11
to clo...@googlegroups.com
Thanks for the explanation All. I have a much better grasp of what's going on now.

Just one more question: It is defined behavior, or should I submit a patch for Clojure 1.3?

Micah

> --
> 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

Stuart Halloway

unread,
Oct 24, 2011, 4:22:35 PM10/24/11
to clo...@googlegroups.com
> Just one more question: It is defined behavior, or should I submit a patch for Clojure 1.3?
>
> Micah

What exactly would the patch do?

Stu

Micah Martin

unread,
Oct 24, 2011, 4:47:55 PM10/24/11
to clo...@googlegroups.com
On Oct 24, 2011, at 3:22 PM, Stuart Halloway wrote:

>> Just one more question: Is it defined behavior, or should I submit a patch for Clojure 1.3?


>>
>> Micah
>
> What exactly would the patch do?

The patch would pass the following test:

(list
(declare ^:dynamic p)
(defn q [] @p))

(should= 10 (binding [p (atom 10)] (q)))

Implementation: I have little experience with the Clojure code base, but I would attempt the following approaches:

* fully evaluate def/declare forms as they are read, similar to the way root level (and do) forms are evaluated immediately.
* figure out what changed between versions 1.2 and 1.3 that caused the behavior difference
* git-bisect the Clojure repo on the above test case ???

Stu, based on your question, I'm sensing that the behavior is in the "undefined" realm…. thoughts?

Micah

Rich Hickey

unread,
Oct 24, 2011, 8:23:13 PM10/24/11
to clo...@googlegroups.com
You should use 'do' for that kind of thing, not list.

Rich

Micah Martin

unread,
Oct 25, 2011, 8:46:28 AM10/25/11
to clo...@googlegroups.com
Right Rich, Thanks. But that was a simplified reduction of real usage. Typically the declare is found in a nested structure:

(describe "something"
(context "fooey"
(with bar 42) ; declare comes from this macro
(it "works"
(should= 42 @bar)))) ; here bar be unbound

I'll see if I can restructure Speclj to get all the Vars declared at the root level. But I'd still like to know….

Is this defined behavior? Or am I jumping through hoops to avoid behavior that doesn't belong?

Micah

Laurent PETIT

unread,
Oct 25, 2011, 3:27:07 PM10/25/11
to clo...@googlegroups.com
2011/10/25 Micah Martin <micah...@gmail.com>:

> Right Rich, Thanks.  But that was a simplified reduction of real usage.  Typically the declare is found in a nested structure:
>
> (describe "something"
>        (context "fooey"
>                (with bar 42)  ; declare comes from this macro
>                (it "works"
>                        (should= 42 @bar)))) ; here bar be unbound
>
> I'll see if I can restructure Speclj to get all the Vars declared at the root level.  But I'd still like to know….
>
> Is this defined behavior?  Or am I jumping through hoops to avoid behavior that doesn't belong?
>

Hi Micah,

maybe this can help : do you know you can create / use vars without
having to have them declared globally ?

See section named "non interned vars" here : http://clojure.org/vars

HTH,

-- Laurent

Reply all
Reply to author
Forward
0 new messages