On Tuesday, September 19, 2017 at 8:01:07 PM UTC-5, John Alan McDonald 
wrote:
>
> I'd like to be able to do something like:
>
> (defn square ^double [^double x] (* x x))
> (def meta-square (with-meta square {:domain Double/TYPE :codomain Double/TYPE 
> :range {:from 0.0 :to Double/POSITIVE_INFINITY :also Double/NaN}})
>
> https://clojure.org/reference/metadata 
> <https://www.google.com/url?q=https%3A%2F%2Fclojure.org%2Freference%2Fmetadata&sa=D&sntz=1&usg=AFQjCNHbXrwRkSRL6pFAprN1DcrQPUEbJA>
>  
> says "Symbols and collections support metadata...". Nothing about whether 
> any other types do or do not support metadata.
>

Functions are probably the big missing thing in that list of types that 
have metadata that you can modify.

A few other things also have metadata that can be set at construction and 
read but not modified after construction: namespaces and the reference 
types (vars, atoms, refs, agents). 
 

> The code above works, at least in the sense that it doesn't throw 
> exceptions, and meta-square is a function that returns the right values, 
> and has the right metadata.
> That's because square is an instance of a class that extends AFunction, 
> which implements IObj (
> https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFunction.java#L18
> ).
>
> It doesn't work, in the sense that it violates "Two objects that differ 
> only in metadata are equal." from https://clojure.org/reference/metadata. 
> That is,
> (= square meta-square) 
> returns false. 
>

I think the tricky thing here is that functions are only equal if they are 
identical. Perhaps it would make sense to implement equals in the function 
hierarchy to somehow "unwrap" the meta wrappers. I'm not sure if that even 
makes sense.

Another option here is this, which I think would be more typical:

(def ^{:domain Double/TYPE :codomain Double/TYPE :range {:from 0.0 :to 
Double/POSITIVE_INFINITY :also Double/NaN}} meta-square square)

that puts the meta on the var (meta-square), not on the function instance 
itself. In this case equality works and the invocation timing should be 
about the same since in both cases you're going through the var to invoke 
the identical function. You can retrieve the meta with (meta #'meta-square) 
since it's on the var.

For my purposes, what really matters is that calling meta-square has 
> roughly 30 times the cost of square itself (and about 3 times the cost of a 
> version without type hints).
>

I see the overhead of your meta-square as more like 2 times the cost in a 
quick test, not sure how you're testing. I'm using 1.9.0-beta1 and Java 8 
and timing 100,000 invocations over a series of runs. There's a lot of 
variability - using something like Criterium would yield better data.
 

> The reason is that meta-square is an instance of a class that extends 
> RestFn (
> https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFunction.java#L26),
>  
> whose invoke() methods are  expensive.
>
> Also, for my purposes, it would actually be better if  "Two objects that 
> differ only in metadata are NOT equal." So perhaps I shouldn't be using 
> metadata at all. It just seems
>
> Options:
>
> (1) Add a meta field to clojure.lang.AFunction (and fix equals and 
> hashcode). I presume the reason there isn't already a meta field is to keep 
> functions as light weight as possible. Are there good benchmarks that I 
> could use to measure the cost of adding an almost always empty field? 
>

I think it would add the cost of an object ref (prob 32 or 64 bits) to 
every function object and I don't think it matters if it's empty or not. I 
don't really know the reason for the current design, would require some 
research. There are a LOT of potential considerations here with respect to 
backwards compatibility, etc. Any change like this would be treated very 
carefully. I do not think the need is necessarily worth such a change, but 
it's hard to weigh that.
 

> (2) Experiments with a mechanical wrapper class (
> https://github.com/palisades-lakes/dynamic-functions/blob/dynesty/src/main/java/palisades/lakes/dynafun/java/MetaFn.java)
>  
> show almost no overhead, but extending that to cover every possible 
> combination of clojure.lang.IFn$DD, clojure.lang.IFn$DLD, ..., is 
> impractical.
>

That's what code gen is for.
 

> (3) Use asm to create a new class that extends the original function's 
> class and implements IObj in the obvious way.
>

The asm included inside Clojure should be considered an internal 
implementation detail, subject to version and API changes without warning.
 

> My short term plan is (2), ignoring the equals violation, and implementing 
> primitive interface wrappers as needed.
>

Will the var version above satisfy?
 

> Are there problems with (3) asm, as a long term solution?
>

As mentioned above, you should not rely on this being available or free 
from breakage. 

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@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+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to