假设你有一个let块中定义的递归函数:
(let [fib (fn fib [n]
(if (< n 2)
n
(+ (fib (- n 1))
(fib (- n 2)))))]
(fib 42))
这可以通过机械方式转换为使用memoize
:
fn
表单打包到memoize
。partial
重新绑定功能符号以执行相同操作。转换上述代码会导致:
(let [fib (memoize
(fn [fib n]
(if (< n 2)
n
(+ (fib fib (- n 1))
(fib fib (- n 2))))))
fib (partial fib fib)]
(fib 42))
这有效,但感觉过于复杂。问题是:有更简单的方法吗?
答案 0 :(得分:3)
我冒风险回答,因为我不是学者,但我不这么认为。你几乎做了标准的事情,通过定点组合器可以部分应用memoization。
你可以尝试使用宏(对于简单的情况,它可能很容易,语法引用会为你做名称解析,你可以对它进行操作)。我回家后会试一试。
编辑:回到家里尝试了一些东西,对于简单的案例来说这似乎是好的
(defmacro memoize-rec [form]
(let [[fn* fname params & body] form
params-with-fname (vec (cons fname params))]
`(let [f# (memoize (fn ~params-with-fname
(let [~fname (partial ~fname ~fname)] ~@body)))]
(partial f# f#))))
;; (clojure.pprint/pprint (macroexpand '(memoize-rec (fn f [x] (str (f x))))))
((memoize-rec (fn fib [n]
(if (< n 2)
n
(+ (fib (- n 1))
(fib (- n 2)))))) 75) ;; instant
((fn fib [n]
(if (< n 2)
n
(+ (fib (- n 1))
(fib (- n 2))))) 75) ;; slooooooow
比我想象的简单!
答案 1 :(得分:1)
我不确定这是&#34;更简单&#34;本身,但我想我会分享一种方法,为我写的CPS变换器重新实现letfn
。
关键是引入变量,但延迟分配它们的值,直到它们都在范围内。基本上,你想写的是:
(let [f nil]
(set! f (memoize (fn []
<body-of-f>)))
(f))
当然这不起作用,因为let
绑定在Clojure中是不可变的。但是,我们可以通过使用引用类型来解决这个问题 - 例如,promise
:
(let [f (promise)]
(deliver! f (memoize (fn []
<body-of-f>)))
(@f))
但这仍然不足,因为我们必须用f
替换<body-of-f>
中(deref f)
的每个实例。但是我们可以通过引入另一个调用存储在promise中的函数的函数来解决这个问题。所以整个解决方案可能如下所示:
(let [f* (promise)]
(letfn [(f []
(@f*))]
(deliver f* (memoize (fn []
<body-of-f>)))
(f)))
如果你有一组相互递归的函数:
(let [f* (promise)
g* (promise)]
(letfn [(f []
(@f*))
(g []
(@g*))]
(deliver f* (memoize (fn []
(g))))
(deliver g* (memoize (fn []
(f))))
(f)))
letfn
样式语法的宏是非常简单的。
答案 2 :(得分:1)
是的,有一种更简单的方法。 它不是功能转换,而是建立在clojure允许的杂质之上。
(defn fib [n]
(if (< n 2)
n
(+ (#'fib (- n 1))
(#'fib (- n 2)))))
(def fib (memoize fib))
第一步几乎以正常方式定义fib
,但是使用进行递归调用var fib
包含。然后重新定义fib
,成为旧自我的备忘版本。
答案 3 :(得分:0)
我认为clojure惯用方法将使用recur
(def factorial
(fn [n]
(loop [cnt n acc 1]
(if (zero? cnt)
acc
(recur (dec cnt) (* acc cnt))
答案 4 :(得分:0)
;; Memoized recursive function, a mash-up of memoize and fn
(defmacro mrfn
"Returns an anonymous function like `fn` but recursive calls to the given `name` within
`body` use a memoized version of the function, potentially improving performance (see
`memoize`). Only simple argument symbols are supported, not varargs or destructing or
multiple arities. Memoized recursion requires explicit calls to `name` so the `body`
should not use recur to the top level."
[name args & body]
{:pre [(simple-symbol? name) (vector? args) (seq args) (every? simple-symbol? args)]}
(let [akey (if (= (count args) 1) (first args) args)]
;; name becomes extra arg to support recursive memoized calls
`(let [f# (fn [~name ~@args] ~@body)
mem# (atom {})]
(fn mr# [~@args]
(if-let [e# (find @mem# ~akey)]
(val e#)
(let [ret# (f# mr# ~@args)]
(swap! mem# assoc ~akey ret#)
ret#))))))
;; only change is fn to mrfn
(let [fib (mrfn fib [n]
(if (< n 2)
n
(+ (fib (- n 1))
(fib (- n 2)))))]
(fib 42))
我的旧Mac上的时间:
原始时间:14089.417441毫秒
mrfn版本,经过的时间:0.220748毫秒