简体   繁体   English

是否可以在clojure的函数内部使用def进行递归定义?

[英]Is there an option to use def inside a function in clojure for a recursive definition?

I'm trying to create an sqrt function using streams in Clojure. 我正在尝试使用Clojure中的流创建sqrt函数。 For that, I need to define the stream inside the function and return it. 为此,我需要在函数内部定义流并返回它。 The problem resides in that the stream is defined in terms of itself; 问题在于流是根据自身定义的。 therefore, using let is not possible, and using def is bogus because it affects the global scope. 因此,不可能使用let,而使用def是伪造的,因为它会影响全局范围。 Is there any way to simulate using a def inside a function that doesn't affect the global scope? 有什么方法可以在不影响全局范围的函数内使用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)

I don't want sqrt-stream to create a global guesses stream. 我不希望sqrt-stream创建全局猜测流。

EDIT 编辑

In clojure the let definition is only available when it has been evaluated. 在clojure中,let定义仅在评估后才可用。 So this definition throws an error. 因此,此定义将引发错误。

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

@CharlesDuffy is right. @CharlesDuffy是正确的。 A promise can be used here: 一个promise可以在这里使用:

(defn sqrt-stream [x]
  (let [p (promise)]

    (deliver p (cons-stream 1.0
                            (stream-map #(sqrt-improve % x) @p)))
    @p))

This does appear to be an XY problem though. 但是,这似乎是一个XY问题。 Just use existing constructs (as shown below in my previous answer). 只需使用现有的构造(如下所示,见上一个答案)。 Also note, stream-map is just the built in map , and you can use a syntax quote ( ` ) to neaten up cons-stream : 还要注意, 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))

I suggest using iterate here though. 我建议在这里使用iterate It repeatedly applies a function to an initial value, and returns an infinite lazy list of the results. 它反复将函数应用于初始值,并返回结果的无限懒惰列表。 This has the same effect as your original code, but relies entirely on core constructs: 这与原始代码具有相同的效果,但是完全依赖于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

Then, use it like: 然后,像这样使用它:

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

You can get a final answer from this by taking as many results as you want to achieve a desired accuracy, then grabbing the final ( last ) value: 通过获取尽可能多的结果以获得所需的精度,然后获取final( last )值,您可以从中获得最终答案:

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

=> 3.162277660168379

I have just noticed that @Carciginate appended a solution based on iterate to the accepted answer some twelve hours before I posted the one that follows. 我刚刚注意到,@ Carciginate在发布以下答案之前约十二小时,在已接受的答案后附加了一个基于iterate的解决方案。


There is no need to set up special functions to manipulate streams. 无需设置特殊功能来操纵流。 The standard sequence functions are designed to manipulate anything that complies with the sequence interface. 标准序列函数旨在处理符合序列接口的任何内容。

Your definitions 您的定义

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

... can be more simply expressed as ...可以更简单地表示为

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

In other words, 换一种说法,

  • stream-car is a synonym for first stream-carfirst的同义词
  • stream-cdr is a synonym for rest . stream-cdrrest的同义词。

Similarly, your cons-stream essentially replicates the old lazy-cons , which is now deprecated. 同样,您的cons-stream实际上是复制了旧的lazy-cons ,现在已弃用。

Your stream-map function adds nothing to the standard map , which is lazy already. 您的stream-map函数没有为标准map添加任何内容,这已经是懒惰了。 it also assumes, wrongly, that only the first sequence may run dry. 还错误地假设只有第一个序列可能会枯竭。

Anyway, you don't need map here. 无论如何,您在这里不需要map A better fit is iterate , which could be defined as 更好的选择是iterate ,它可以定义为

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

We can then define your sqrt-stream as 然后,我们可以将您的sqrt-stream定义为

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

For example, 例如,

=> (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)

There are problems which stretch Clojure's sequence repertoire, but this isn't one of them. 有一些问题可以扩展Clojure的序列表,但这不是问题之一。

Sorry to seem so negative, but a fair amount of thought has gone into designing Clojure to avoid the replication of sequence functions that some previous Lisps have been prone to. 很抱歉,看起来如此消极,但是在设计Clojure时已经考虑了很多想法,以避免重复某些以前的Lisps倾向于使用的序列函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM