Reputation: 144
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))
Upvotes: 0
Views: 476
Reputation: 13483
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.
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-cdr
is a synonym for rest
.Similarly, your cons-stream
essentially replicates the old lazy-cons
, which is now deprecated.
Your stream-map
function adds nothing to the standard map
, which is lazy already. it also assumes, wrongly, that only the first sequence may run dry.
Anyway, you don't need map
here. A better fit is iterate
, which could be defined as
(defn iterate [f x]
(lazy-seq (cons x (iterate f (f x)))))
We can then define your sqrt-stream
as
(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.
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.
Upvotes: 1
Reputation: 45806
@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
Upvotes: 2