【问题标题】:Is there an option to use def inside a function in clojure for a recursive definition?是否可以选择在 clojure 的函数中使用 def 进行递归定义?
【发布时间】:2019-10-18 01:46:36
【问题描述】:

我正在尝试使用 Clojure 中的流创建一个 sqrt 函数。为此,我需要在函数内部定义流并返回它。问题在于流是根据自身定义的。因此,使用 let 是不可能的,使用 def 是虚假的,因为它会影响全局范围。有没有办法在不影响全局范围的函数中模拟使用 def ?

(defmacro cons-stream [a b]
  (list 'lazy-seq (list 'cons a (list 'lazy-seq b))))

(defn stream-car [stream] (first stream))

(defn stream-cdr [stream] (rest stream))

(defn stream-map [proc & streams]
  (if (empty? (first streams))
    '()
    (cons-stream (apply proc (map stream-car streams))
                 (apply stream-map proc (map stream-cdr streams)))))

(defn average [a b] (/ (+ a b) 2.0))

(defn sqrt-improve [guess x]
  (average guess (/ x guess)))

(defn sqrt-stream [x]
  (def guesses (cons-stream 1.0
                            (stream-map #(sqrt-improve % x) guesses)))
  guesses)

我不希望 sqrt-stream 创建全局猜测流。

编辑

在 clojure 中,let 定义仅在被评估后才可用。所以这个定义会抛出一个错误。

(defn sqrt-stream [x]
  (let [guesses (cons-stream 1.0
                             (stream-map #(sqrt-improve % x) guesses))]
    guesses))

【问题讨论】:

  • 我看不出“因此,不可能使用 let”(当递归定义值时)声明是正确的(只要您确保存在结束条件)。您能否详细说明您尝试了什么以及它是如何失败的?
  • (响应编辑)...对,但您可以使用 let 将 promise/fn/other 句柄绑定到延迟执行。据我所知,这就是您尝试使用 def 隐式执行的操作;最好使用明确的语法。

标签: clojure


【解决方案1】:

我刚刚注意到@Carciginate 在我发布下面的答案前十二小时在已接受的答案中附加了一个基于iterate 的解决方案。


无需设置特殊功能来操作流。 standard sequence functions 旨在操作符合序列接口的任何内容。

你的定义

(defn stream-car [stream] (first stream))
(defn stream-cdr [stream] (rest stream))

... 可以更简单地表示为

(def stream-car first)
(def stream-cdr rest)

换句话说,

  • stream-carfirst 的同义词
  • stream-cdrrest 的同义词。

同样,您的cons-stream 基本上复制了旧的lazy-cons,现在已弃用。

您的stream-map 函数对标准map 没有任何添加,后者已经很懒惰了。它还错误地假设只有第一个序列可能会干涸。

无论如何,这里不需要map。更合适的是iterate,可以定义为

(defn iterate [f x]
  (lazy-seq (cons x (iterate f (f x)))))

然后我们可以将您的sqrt-stream 定义为

(defn sqrt-stream [x]
  (iterate
    (fn [guess] (sqrt-improve guess x))
    1.0))

例如,

=> (take 10 (sqrt-stream 10))
(1.0 5.5 3.659090909090909 3.196005081874647 3.16245562280389 
 3.162277665175675 3.162277660168379 3.162277660168379 
 3.162277660168379 3.162277660168379)

Clojure 的序列库存在一些问题,但这不是其中之一。

很抱歉,看起来如此消极,但在设计 Clojure 时已经考虑了很多,以避免重复一些以前的 Lisps 容易出现的序列函数。

【讨论】:

    【解决方案2】:

    @CharlesDuffy 是对的。此处可以使用promise

    (defn sqrt-stream [x]
      (let [p (promise)]
    
        (deliver p (cons-stream 1.0
                                (stream-map #(sqrt-improve % x) @p)))
        @p))
    

    这似乎是一个 XY 问题。只需使用现有的构造(如下面的我之前的回答所示)。另请注意,stream-map 只是内置的map,您可以使用语法引号(`)来整理cons-stream

    (defmacro cons-stream [a b]
      `(lazy-seq (cons ~a (lazy-seq ~b)))) ; ~ unquotes a and b
    
    (defn average [a b]
      (/ (+ a b) 2.0))
    
    (defn sqrt-improve [guess x]
      (average guess (/ x guess)))
    
    (defn sqrt-stream [x]
      (let [p (promise)]
    
        (deliver p (cons-stream 1.0
                                (map #(sqrt-improve % x) @p)))
        @p))
    

    我建议在这里使用iterate。它重复地将函数应用于初始值,并返回结果的无限惰性列表。这与您的原始代码具有相同的效果,但完全依赖于 core 构造:

    (defn average [a b]
      (/ (+ a b) 2.0))
    
    (defn sqrt-improve [guess x]
      (average guess (/ x guess)))
    
    (defn sqrt-stream [n]
      (iterate #(sqrt-improve % n) ; Apply this function over and over again
               1.0)) ; The initial value to iterate over
    

    然后,像这样使用它:

    (->> (sqrt-stream 10)
         (take 10))
    
    =>
    (1.0
     5.5
     3.659090909090909
     3.196005081874647
     3.16245562280389
     3.162277665175675
     3.162277660168379
     3.162277660168379
     3.162277660168379
     3.162277660168379)
    

    您可以通过获取尽可能多的结果来获得最终答案以达到所需的准确度,然后获取最终 (last) 值:

    (->> (sqrt-stream 10)
         (take 100)
         (last))
    
    => 3.162277660168379
    

    【讨论】:

    • 我正在阅读 SICP,并使用 clojure 在那里实现代码。对于特定的 sqrt-stream 问题,您的建议是正确的。但是,问题是是否有一种方法可以在函数内部使用 def 而不会弄乱全局作用域,就像 def 允许的局部作用域的递归定义一样。通过查看 SICP 3.5.3 节中的代码 sn-ps 可以看出我的担忧。
    • @SantiagoCabrera 根据 CharlesDuffy 的建议进行了更新。我仍然认为你不应该在这里重新发明轮子。如map的存在所示; Clojure 已经内置了您需要的大部分工具。
    • 谢谢,使用 promise 正是我所需要的。这里的代码仅供学习。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-14
    • 2010-09-08
    相关资源
    最近更新 更多