Search code examples
clojure

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. 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. Is there any way to simulate using a def inside a function that doesn't affect the global scope?

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

EDIT

In clojure the let definition is only available when it has been evaluated. So this definition throws an error.

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

Solution

  • @CharlesDuffy is right. A promise can be used here:

    (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. 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:

    (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. 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:

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

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