使用“可能ARef”在高阶函数中 [英] Using "maybe ARefs" in higher-order functions

查看:95
本文介绍了使用“可能ARef”在高阶函数中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道如何包装一个函数(或函数定义),使它看起来不知道传递给它的参数是可变的还是不可变的 - 但是如果给定的任何参数是可变的,它应该解引用



我可以写一个函数,它要求每个参数都是可变的存储空间,然后在每次被调用时解除引用。但是有一个性能命中(非常,非常小,我知道!)解除引用在Clojure的可变存储。在我的具体使用案例中,这些实际上是瓶颈操作,小到足以解除引用以产生差异,并重复数十万到数百万次(更多在我的用例下面,但现在让我们假设这是重要的)。所以我不想在我不需要它是可变的情况下使用可变数据。这将是很好的,如果,从外部,代码似乎不在乎是否初始参数是可变的或不可变的。为了简单起见,我们假设函数如下:

 (defn foo [ab] 
(fn [x](* abx)))
(def instance(foo 3 4))
(instance 5); < - 60
(instance 8); < - 96

我想要一个 foo 这是聪明的做到这一点:

 (def a(agent 3))
4))
(foo ab); < - (fn [x](*(deref a)(deref b)x))
(foo a 4); < - (fn [x](*(deref a)4 x))
(foo 3 4); < - (fn [x](* 3 4 x))

做一些使用引用和取消引用(自然,对吗?这是宏使用!),它给了我一个讨厌的错误嵌入对象在代码(一个非常类似的问题,不同的用例,是此处讨论)。我的下一次尝试给了我一个奇怪的(和大规模的)运行时减速


$ b $

我在使用一些机器学习算法。在典型的情况下,用户将初始化具有一组特定参数的算法,然后在一组数据上运行它。但有时,用户/用户定义的代码可能希望在算法运行时修改参数,或者基于时间(例如,),或者基于在观察算法的进行性能时确定的一些其他标准。我的算法是并行的,每个线程都需要看到的变化。

似乎我在

中回答了这个问题href =http://stackoverflow.com/questions/23039818/modifying-expressions-at-runtime/23042967?noredirect=1#comment35210949_23042967>相关问题与最后的可能deref -expr 示例。该代码在这里重复了 Timothy Dean自己的回答,以及他为此写的一些很好的宏糖,所以一定要签出他的答案。这是稍微修改版本的 maybe-deref-expr ,也许更容易阅读。

 (defn maybe-deref-expr 
[values params body]
(let [valmap(zipmap params values)
deref?#(instance?clojure.lang.IDeref %)
body *(clojure.walk / postwalk
#(if(deref?(valmap%))`(deref〜%)%)
body)
gen `(fn〜params〜body *))]
(应用基因值)))

使用Timothy Dean的宏糖

 (defmacro defn-ref-agnostic 
[name params body]
`($ n $ name
〜params
(maybe-deref-expr〜params'〜params'〜body)))

如果我们这样做

 (defn-ref-agnostic add 
[ ab]
(+ ab))

需要时自动解除引用

 $  (add 40 2); => 42 
(add(ref 40)(atom 2)); => 42

但是,用例不是自己定义函数,而是函数生成器关闭其他参数

 (defn-ref-agnostic add-to 
[ab]
(fn [x] (+ abx)))

现在如果我们这样做了

 (def baz1(add-to 40 2))

(def my-ref(ref 40))
原子(原子2))

(def baz2(add-to my-ref my-atom))

然后,当 baz1 baz2时,我们采取 eval 被定义,并且不是。为 baz1 baz2 的定义生成的代码,以及这些代码的使用时的性能,我们做了

 (def baz1(fn [x](+ 40 2 x)))
(def baz2 (fn [x](+ @ my-ref @ my-atom x)))

已经说过...



原来的Without Eval解决方案,如果它适合你的用例,是我想要的: p>

 (defn foo [ab] 
(let [[fa fb](map#(if(instance?clojure.lang .ideref%)
deref
identity)
[ab])]
(fn [x](+ fa a)(fb b)x))) $ b

这只引入了一个额外的间接层次,只有最低两个额外的身份函数调用的低成本。它比上面简单很多,可以非常灵活。这与其他相关问题的答案之间的主要区别是,测试/分支已移到返回的函数之外,现在关闭的结果。


I want to know how to wrap a function (or function definition) such that it becomes seemingly agnostic to whether the parameters passed to it are mutable or immutable -- but if any parameter it is given is mutable, it should dereference that parameter every time it is called, to get the current value.

I could write a function that requires each parameter to be mutable storage that it then dereferences each time it is called. But there's a performance hit (very, very small, I know!) to dereferencing mutable storage in Clojure. In my specific use case these actually are bottleneck operations, small enough for dereferencing to make a difference, and repeated hundreds of thousands to millions of times (more on my use case below, but for now let's just assume this is significant). So I don't want to use mutable data in the cases where I don't need it to be mutable. It would be nice if, from the outside, the code appeared not to care whether the initial parameters were mutable or immutable. Let's say, for simplicity's sake, that the function is the following:

(defn foo [a b]
    (fn [x] (* a b x)))
(def instance (foo 3 4))
(instance 5) ; <- 60
(instance 8) ; <- 96

I would like a foo that is smart enough to do this:

(def a (agent 3))
(def b (agent 4))
(foo a b) ; <- (fn [x] (* (deref a) (deref b) x))
(foo a 4) ; <- (fn [x] (* (deref a) 4 x))
(foo 3 4) ; <- (fn [x] (* 3 4 x))

However, my first attempt to do something used quoting and unquoting (natural, right? It's what macros use!), and it gave me a nasty error about embedding objects in code (a very similar issue, different use-case, is discussed here). My next attempt gave me a weird (and massive) slowdown in runtimes.

Does anyone know of a good way to do this?

Background

I am working some machine learning algorithms. In a typical scenario, the user would initialize an algorithm with a certain set of parameters, and then run it on a set of data. But sometimes a user/user-defined code might want to modify the parameters as the algorithm is running, either based on time (e.g., simulated annealing), or based on some other criteria determined while watching the algorithm's ongoing performance. My algorithms are parallelized, and each thread would need to see the change. Restarting the algorithm when I'm changing the parameters would defeat the purpose.

解决方案

It seems I answered this question in the related question with the last maybe-deref-expr example there. That code is repeated in Timothy Dean's own answer here, along with some nice macro sugar he wrote for it, so definitely check out his answer too. Here's a slightly modified version of maybe-deref-expr, perhaps a bit easier to read.

(defn maybe-deref-expr 
  [values params body] 
  (let [valmap (zipmap params values)
        deref? #(instance? clojure.lang.IDeref %)
        body* (clojure.walk/postwalk 
                #(if (deref? (valmap %)) `(deref ~%) %) 
                body)
        gen (eval `(fn ~params ~body*))] 
    (apply gen values)))

With Timothy Dean's macro sugar

(defmacro defn-ref-agnostic 
  [name params body]
  `(defn ~name 
     ~params
     (maybe-deref-expr ~params '~params '~body)))

if we do

(defn-ref-agnostic add
  [a b]
  (+ a b))

Then we get a slow (eval hit) add that is automatically dereferences when needed

(add 40 2) ;=> 42
(add (ref 40) (atom 2)) ;=> 42

But, the use case is not to define functions themselves, but function generators that close over other parameters.

(defn-ref-agnostic add-to
  [a b]
  (fn [x] (+ a b x)))

Now if we do

(def baz1 (add-to 40 2))

(def my-ref (ref 40))
(def my-atom (atom 2))

(def baz2 (add-to my-ref my-atom))

Then we take the eval hit when baz1 and baz2 are defined, and not when they are subsequently used. The code produced for the definition of baz1 and baz2, and thus the performance of those when used, is exactly as if we had done

(def baz1 (fn [x] (+ 40 2 x)))
(def baz2 (fn [x] (+ @my-ref @my-atom x)))

That having been said...

The original "Without Eval" solution, if it fits your use case, is what I would prefer:

(defn foo [a b]
  (let [[fa fb] (map #(if (instance? clojure.lang.IDeref %) 
                        deref 
                        identity) 
                     [a b])]
      (fn [x] (+ (fa a) (fb b) x))))

This introduces an extra level of indirection only at the low, low cost of at most two extra identity function calls. It is a lot simpler than the above and can be very flexible. The main difference between this and the answer to the other related question is that the test/branching has been moved outside the returned function, which now closes over the results.

这篇关于使用“可能ARef”在高阶函数中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆