let函数内部的递归

Posted

技术标签:

【中文标题】let函数内部的递归【英文标题】:Recursion inside let function 【发布时间】:2012-10-08 22:23:50 【问题描述】:

我对 def 和 let 如何以不同方式绑定变量感到困惑。有人可以向我解释为什么会这样吗:

(def leven
  (memoize
   (fn [x y]
     (cond (empty? x) (count y)
           (empty? y) (count x)
           :else (min (+ (leven (rest x) y) 1)
                      (+ (leven x (rest y)) 1)
                      (+ (leven (rest x) (rest y)) (if (= (first x) (first y)) 0 1)))))))

但是当我尝试将函数声明为 let 时编译失败:

(def leven
  (let [l (memoize (fn [x y]
                     (cond (empty? x) (count y)
                           (empty? y) (count x)
                           :else (min (+ (l (rest x) y) 1)
                                      (+ (l x (rest y)) 1)
                                      (+ (l (rest x) (rest y)) (if (= (first x) (first y)) 0 1))))))]
    (l x y)))

编辑:这行得通,使用 Ankur 展示的技术。

(defn leven [x y]
  (let [l (memoize (fn [f x y]
                     (cond (empty? x) (count y)
                           (empty? y) (count x)
                           :else (min (+ (f f (rest x) y) 1)
                                      (+ (f f x (rest y)) 1)
                                      (+ (f f (rest x) (rest y)) (if (= (first x) (first y)) 0 1))))))
        magic (partial l l)]
    (magic x y)))

【问题讨论】:

【参考方案1】:

以下是执行您要求的示例。为了简单起见,我使用阶乘,并在阶乘中添加了 println 以确保记忆工作正常

(let [fact (memoize (fn [f x] 
                       (println (str "Called for " x))
                       (if (<= x 1) 1 (* x  (f f (- x 1))))))
      magic (partial fact fact)] 
     (magic 10)
     (magic 11))

首先计算 10 的阶乘,然后计算 11,在这种情况下,它不应该再次调用 10 到 1 的阶乘,因为已经记住了。

Called for 10
Called for 9
Called for 8
Called for 7
Called for 6
Called for 5
Called for 4
Called for 3
Called for 2
Called for 1
Called for 11
39916800

【讨论】:

非常有趣。所以你基本上只是将函数作为参数传入,这样编译器就不会因为它没有被定义而感到困惑。我现在无法尝试,但我稍后会尝试这种方法。【参考方案2】:

let 表单按顺序绑定名称,因此在您的第二个函数定义中,名称 l 在您尝试引用它时不存在。您可以使用letfn(带有一些小修改)或给定义的函数一个名称,然后改为引用它,如下所示:

(def leven  
  (let [l (memoize (fn SOME-NAME [x y]
    (cond 
      (empty? x) (count y)
      (empty? y) (count x)
      :else (min (+ (SOME-NAME (rest x) y) 1)
                 (+ (SOME-NAME x (rest y)) 1)
                 (+ (SOME-NAME (rest x) (rest y)) (if (= (first x) (first y)) 0 1))))))]
l))

您可能会注意到,我将 let 的返回值更改为 l 本身,因为这是您希望 leven 绑定的内容。 (l x y) 是有问题的,因为它只引用了函数本地的绑定,let 无法访问。

【讨论】:

这样使用 SOME-NAME 功能不会失去 memoize 的好处吗?难道你不需要调用函数memoize返回,或者在let语句中不可能有一个递归的memoized函数? @onit 可以修改leven 的定义以获得记忆的好处,方法是将SOME-NAME 作为第一个参数:(fn [SOME-NAME x y],然后还将对SOME-NAME 的调用替换为(SOME-NAME SOME-NAME ...) 最后将返回值 l 替换为 (partial l l)

以上是关于let函数内部的递归的主要内容,如果未能解决你的问题,请参考以下文章

内部有循环的递归函数

如何在函数内部使用递归查询?

Haskell 尾递归内部函数

内部有闭包的递归函数

递归创建决策树

python 函数的递归操作