How do you handle closures?
EG:

(defn squared ^IFn [^IFn f]
  (with-meta
    (fn squared0 [x]
      (let [fx (double (f x))]
        (* fx fx)))
    {:domain :number
     :range :positive-number}))

I think you can do it, but it would require the macro taking apart the
value of &env, which means depending on clojure.lang.Compiler.LocalBinding,

My guess is that using ASM to adds fields and methods to a class would be
less dependent on implementation details, but having never done that, it's
only a guess.


On Wed, Sep 20, 2017 at 1:30 PM, Justin Smith <noisesm...@gmail.com> wrote:

> You don't need to indirect - the invoke, call, and apply methods can have
> your code in them directly (a macro can expand to put your code body into
> them directly as appropriate). If you need to ensure it isn't mistaken for
> data, you could add a marker interface and check for it (an interface with
> no methods, just used for checking membership).
>
> Why would you implement IPersistentMap instead of IObj, wouldn't it be
> both, if anything? A normal hash-map implements both for example.
>
> To implement your own bytecode compiler just so you can have something
> that carries data that counts in equality checks seems a bit overboard to
> me, since a record implementing IFn does this for very little trouble.
>
> On Wed, Sep 20, 2017 at 12:58 PM John McDonald <palisades.la...@gmail.com>
> wrote:
>
>> I've done something like this in the past.
>>
>> I'd expect the performance to be similar to MetaFn. In either case, I
>> think you have a wrapper class that carries the additional data, and one
>> level of indirection for invoke or invokePrim. My benchmarks results so far
>> seem to show the indirection gets optimized away eventually.
>>
>> I think the main difference is that the function now implements something
>> like IPersistentMap rather than IObj. The problem I had was if the function
>> was some Map-like object to begin with, I'd get confused about what was
>> 'data' and what was 'metadata'.
>>
>> On Wed, Sep 20, 2017 at 12:04 PM, Justin Smith <noisesm...@gmail.com>
>> wrote:
>>
>>> I've had good luck with an approach suggested by Kevin Downey, defining
>>> a defrecord that implements IFn so that it works when called and applied,
>>> and transparently supporting attached data as if it were a hash-map. It's
>>> not too hard to implement if you know the precise arg count you need to
>>> support, and I'd be interested to see how the performance compares.
>>>
>>> On Wed, Sep 20, 2017 at 11:57 AM John McDonald <
>>> palisades.la...@gmail.com> wrote:
>>>
>>>> 2nd issue: Benchmarks
>>>>
>>>> I use both criterium and simple 'run repeatedly and divide the clock
>>>> time'.
>>>>
>>>> I've had trouble getting consistent results from run to run with either.
>>>>
>>>> Most recently (yesterday) I've added many more warmup runs, giving
>>>> HotSpot lots of time to do its stuff, which seems to be stabilizing the
>>>> results.
>>>> This might be unfair, because in practice a lot of important code might
>>>> never run that many times.
>>>> At least, it gives the limiting performance of code that runs a lot.
>>>>
>>>> Unfortunately, this benchmark is split over 3 github projects, so it
>>>> might be a little hard to follow.
>>>>
>>>> The task I'm timing is computing the sum of squares of the elements in
>>>> a moderately large (4m elements) double[] array (aka L2Norm).
>>>>
>>>> I've compared 8 implementations, all of which accumulate the sum in
>>>> Java.
>>>>
>>>>   6.37ms inline --- naive sum of squares in Java.
>>>>   6.37ms invokestatic --- call a static method in Java to square the
>>>> elements.
>>>>   6.38ms primitive --- calls square.invokePrim(x[i]) to square the
>>>> elements
>>>>   6.37ms boxprimitive --- calls square.invoke(x[i]) to square the
>>>> elements
>>>>   6.37ms funxprimitive --- calls funxSquare.invokePrim(), where
>>>> funxSquare is square wrapped with MetaFn, an experiment metadata wrapper.
>>>>   6.38ms funxboxed --- calls funxSquare.invoke()
>>>>  43.55ms boxed --- calls boxedSquare.invoke() , where boxedSquare is a
>>>> version of square without type hints.
>>>> 151.61ms cljmeta --- calls metaSquare.invoke(), where metasquare is the
>>>> result of (with-meta square {...})
>>>>
>>>> Clojure 1.8.0, Oracle JDK 1.8, Win10, Lenovo X1 i5-7300U.
>>>> Sum of squares for each of 2 double[4194304], in 2 threads,
>>>> concurrently.
>>>>
>>>> boxed and cljmeta create a lot of garbage, causing their clock times to
>>>> be more variable, depending on exactly what GC does..
>>>> It's possible they appear relatively faster with a small number of
>>>> total calls, if they don't trigger a GC, and HotSpot doesn't fully optimize
>>>> the others.
>>>> I think this is a valid usage pattern, but many many calls to small
>>>> functions is the case that I'm interested in at present.
>>>>
>>>>
>>>> Main scripts:
>>>>
>>>> Using criterium:
>>>>  https://github.com/palisades-lakes/function-experiments/
>>>> blob/dynesty/src/scripts/clojure/palisades/lakes/funx/l2norm/bench.clj
>>>>
>>>> Running the benchmark 4k times and dividing the clock time:
>>>> https://github.com/palisades-lakes/function-experiments/
>>>> blob/dynesty/src/scripts/clojure/palisades/lakes/funx/l2norm/msec.clj
>>>>
>>>> These both use general benchmarking code from
>>>> https://github.com/palisades-lakes/benchtools
>>>>
>>>> The experimental metadata function wrapper is in:
>>>> https://github.com/palisades-lakes/dynamic-functions/blob/
>>>> dynesty/src/main/java/palisades/lakes/dynafun/java/MetaFn.java
>>>>
>>>>
>>>> On Wed, Sep 20, 2017 at 11:17 AM, John McDonald <
>>>> palisades.la...@gmail.com> wrote:
>>>>
>>>>> Thanks for the quick response.
>>>>>
>>>>> One issue at a time:
>>>>>
>>>>> (A) Putting metadata on Vars instead of on the functions themselves:
>>>>>
>>>>> I need to be able to associate facts with the function instances. I
>>>>> can't rely on every function being bound to a Var.
>>>>> For example, I'm constructing cost functions for machine learning, and
>>>>> other applications, by summing, composing, etc. other functions.
>>>>>
>>>>>
>>>>>
>>>>> On Tue, Sep 19, 2017 at 11:34 PM, Alex Miller <a...@puredanger.com>
>>>>> wrote:
>>>>>
>>>>>>
>>>>>>
>>>>>> 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 a topic in
>>>>>> the Google Groups "Clojure" group.
>>>>>> To unsubscribe from this topic, visit https://groups.google.com/d/
>>>>>> topic/clojure/D8mksieuUPI/unsubscribe.
>>>>>> To unsubscribe from this group and all its topics, send an email to
>>>>>> clojure+unsubscr...@googlegroups.com.
>>>>>> For more options, visit https://groups.google.com/d/optout.
>>>>>>
>>>>>
>>>>>
>>>> --
>>>> 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.
>>>>
>>> --
>>> 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 a topic in the
>>> Google Groups "Clojure" group.
>>> To unsubscribe from this topic, visit https://groups.google.com/d/
>>> topic/clojure/D8mksieuUPI/unsubscribe.
>>> To unsubscribe from this group and all its topics, send an email to
>>> clojure+unsubscr...@googlegroups.com.
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>
>> --
>> 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.
>>
> --
> 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 a topic in the
> Google Groups "Clojure" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/
> topic/clojure/D8mksieuUPI/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> clojure+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

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