Two issues: "eval of empty defrecord loses type" and "emit-hinted-impl expands to 'fn not `fn"

85 views
Skip to first unread message

Jason Wolfe

unread,
Apr 10, 2013, 3:15:27 AM4/10/13
to cloju...@googlegroups.com
While implementing a new library for schema definitions and function validation, I ran into two unexpected issues in Clojure 1.5.1 for which I've created tickets.  I searched for previous tickets and discussion before submitting, so apologies if these are off-base or already covered.  

evaluating an empty defrecord loses the record type information:

user> (defrecord Foo [])
user.Foo
user> (eval (Foo.))
{}

user> (defrecord Foo [a b])
user.Foo
user> (eval (Foo. 1 2))
#user.Foo{:a 1, :b 2}

This came up in code that uses records to describe argument schema information for functions in a macro, with no direct use of 'eval'. It is unexpected because of the inconsistency between empty and non-empty defrecords, and because the Clojure docs say 'Any object other than those discussed above will evaluate to itself.'

emit-hinted-impl expands to non-ns-qualified invocation of 'fn

user> (ns tmp (:refer-clojure :exclude [fn]))
(defprotocol Foo (foo [this]))
(extend-protocol Foo Object (foo [this]))

yields 

CompilerException java.lang.RuntimeException: Unable to resolve symbol: fn in this context, compiling:(NO_SOURCE_PATH:5:1) 

This makes it difficult to construct a namespace that provides a replacement def for fn and also extends a protocol.
Perhaps 'fn in the implementaiton of emit-hinted-impl should be `fn (with a backquote)?

I can work around both of these in the mean-time, and potentially provide patches (especially for #2, which seems to be a simple fix), but if someone has time to see if these are considered bugs or my own issues I would really appreciate it so we can figure out how to best proceed.  Thanks in advance for your help.

-Jason




Jason Wolfe

unread,
Apr 10, 2013, 12:52:23 PM4/10/13
to cloju...@googlegroups.com
I guess (1) is a duplicate of an existing issue, sorry for the noise.  

I also ran into two more odd behaviors, which I couldn't find in JIRA -- should I add tickets for either of these?

3.  with-meta ruins primitive fn

user> (def f (fn [^long x] x))
#'user/f
user> (.invokePrim (with-meta f {}) 1)
IllegalArgumentException No matching method found: invokePrim for class clojure.lang.AFunction$1  clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:53)
user> (contains? (ancestors (class f)) clojure.lang.IFn$LO)
true
user> (contains? (ancestors (class (with-meta f {}))) clojure.lang.IFn$LO)
false

4.  behavior for record value evaluation does not follow that of PersistentMap:

user> (defrecord A [x])
user.A
user> (eval (A. `long))
#user.A{:x clojure.core/long}
user> (eval {:x `long})
{:x #<core$long clojure.core$long@5de54eb7>}

Thanks for your help,
Jason

Andy Fingerhut

unread,
Apr 10, 2013, 1:18:21 PM4/10/13
to cloju...@googlegroups.com
On Wed, Apr 10, 2013 at 9:52 AM, Jason Wolfe <ja...@w01fe.com> wrote:
I also ran into two more odd behaviors, which I couldn't find in JIRA -- should I add tickets for either of these?

4.  behavior for record value evaluation does not follow that of PersistentMap:

user> (defrecord A [x])
user.A
user> (eval (A. `long))
#user.A{:x clojure.core/long}
user> (eval {:x `long})
{:x #<core$long clojure.core$long@5de54eb7>}

First note the difference between these two expressions.  The second one is effectively eval'ing the expression 2 times, once because it is an argument to a function (eval), and another time because of eval.  The first one is only eval'ing it once, because of the quote.

user=> (eval '(cons + '(1 2)))
(#<core$_PLUS_ clojure.core$_PLUS_@12944313> 1 2)
user=> (eval (cons + '(1 2)))
3

I think the sequence below might help clarify the different behaviors here.  It still might be a bug -- that isn't clear to me yet.

user=> (A. `long)
#user.A{:x clojure.core/long}
user=> (eval '(A. `long))
#user.A{:x clojure.core/long}
user=> (eval (eval '(A. `long)))
#user.A{:x clojure.core/long}

user=> {:x `long}
{:x clojure.core/long}
user=> (eval '{:x `long})
{:x clojure.core/long}
user=> (eval (eval '{:x `long}))
{:x #<core$long clojure.core$long@6eb8cab9>}

Andy

Jason Wolfe

unread,
Apr 10, 2013, 11:38:04 PM4/10/13
to cloju...@googlegroups.com
Thanks for your response.  I believe there might be a difference here I'm not understanding, but I'm not sure what it is -- can you elaborate on your point about one eval versus two?  Some more examples to attempt to amplify the parallelism:

user> (eval (hash-map :x `long))
{:x #<core$long clojure.core$long@5de54eb7>}
user> (eval (->A `long))
#user.A{:x clojure.core/long}
user> (eval (map->A (hash-map :x `long)))
#user.A{:x clojure.core/long}

and in case it matters, here's a simplified version of the real use case where this came up, with no eval -- just a macro:

user> (defmacro munge-meta1 [x] (assoc x :schema (->A (:schema (meta x)))))
#'user/munge-meta1
user> (munge-meta1 ^{:schema long} {})
{:schema #user.A{:x long}}

user> (defmacro munge-meta2 [x] (assoc x :schema (hash-map :x (:schema (meta x)))))
#'user/munge-meta2
user> (munge-meta2 ^{:schema long} {})
{:schema {:x #<core$long clojure.core$long@5de54eb7>}}

This seems to be fixed by moving the record creation post-evaluation, so it's not a big deal, just surprising (plus I haven't yet convinced myself that this will always work if the user's schema itself contains record-creating forms, although it seems to work OK):

user> (defmacro munge-meta1 [x] (assoc x :schema `(->A ~(:schema (meta x)))))
#'user/munge-meta1
user> (munge-meta1 ^{:schema long} {})
{:schema #user.A{:x #<core$long clojure.core$long@5de54eb7>}}

Thanks again,
Jason 

Jason Wolfe

unread,
Apr 13, 2013, 1:32:45 AM4/13/13
to cloju...@googlegroups.com
I created issues for these, in case they are considered bugs:


The metadata issue with primitive fns in particular is not something I see an easy workaround for, so if you are open to a patch we would be happy to try to help with that.

Thanks,
Jason
Reply all
Reply to author
Forward
0 new messages