4

In my answer at Clojure For Comprehension example I have a function that processes its own output:

(defn stream [seed]
  (defn helper [slow]
    (concat (map #(str (first slow) %) seed) (lazy-seq (helper (rest slow)))))
  (declare delayed)
  (let [slow (cons "" (lazy-seq delayed))]
    (def delayed (helper slow))
    delayed))

(take 25 (stream ["a" "b" "c"]))
("a" "b" "c" "aa" "ab" "ac" "ba" "bb" "bc" "ca" "cb" "cc" "aaa" "aab" "aac" 
"aba" "abb" "abc" "aca" "acb" "acc" "baa" "bab" "bac" "bba")

It works by creating a forward reference (delayed) which is used as the second entry in a lazy sequence (slow). That sequence is passed to the function, which is lazy, and the output from that function (the very first part of the lazy sequence, which does not require the evaluation of delayed) is then used to set the value of delayed.

In this way I "tie the knot". But this is done much more elegantly in Haskell (eg. Explanation of “tying the knot”). Given that Clojure has delay and force, I wondered if there was a better way to do the above?

The question then: can the (ugly, explicit) mutation (of delayed) somehow be avoided in the code above? Obviously(?) you still need mutation, but can it be hidden by "lazy" constructs?

[I had a question last night with a similar title when I was still trying to understand how to do this; no one replied before the code above worked, so I deleted it, but I am not really happy with this approach so am trying again.]

See also: Must Clojure circular data structures involve constructs like ref? (kinda frustrating that people are duplicating questions).

Community
  • 1
  • 1
andrew cooke
  • 45,717
  • 10
  • 93
  • 143

1 Answers1

2

I'm not sure I can answer the question for the general case, but this function seems to solve the particular case.

(defn stream
  [seed]
  (let [step (fn [prev] (for [p prev s seed] (str p s)))]
    (for [x (iterate step seed) y x] y)))

Although I ran into a out of memory exception for a large (dorun (take ...)). So there probably is an issue with this function.

kotarak
  • 17,099
  • 2
  • 49
  • 39
  • it will inherently have memory issues because it has to keep an increasingly large chunk of the sequence in the loop (the output grows faster than then it is consumed as input). that's unavoidable, so don't worry about that. – andrew cooke Jul 19 '12 at 20:56