Search code examples
clojure

Idiomatic Clojure way of mimicking Python's yield


I'm iterating through a list, building up state as I go, and occasionally when I encounter a certain sentinel, I return a result. If I was doing this in Python, I would lazily yield the results, tracking state in the function's local scope as I go:

# this is simplified for illustration
def yielder(input_list):
    state = 0
    for item in input_list:
        if item = 'SENTINEL':
            yield state * 2
            state = 0
        else:
            state += item

yielder([1, 5, 2, 5, 'SENTINEL', 4, 6, 7]) # [26, 34]

My first implementation uses reduce, but that's not as good as yield because:

  • The value I pass between iterations has both the loop state, and the items's i want to yield, which seems clunky
  • It's not lazy

iterate could be used to mitigate the latter, but i don't actually want to return something for every input item, so it would require more munging.

What's an idiomatic way to do this in Clojure?


Solution

  • You can build this yourself using lazy-seq as you mention or you could use partition and reduce to split the problem into phases then thread them together. I'll use the thread-last macro to show each step on it's own:

    user> (->> [1, 5, 2, 5, :SENTINEL, 4, 6, 7] ;; start with data
               (partition-by #(= :SENTINEL %))  ;; ((1 5 2 5) (:SENTINEL) (4 6 7))
               (take-nth 2)                     ;; ((1 5 2 5) (4 6 7))
               (map #(* 2 (reduce + %))))       ;; the map here keeps it lazy
    (26 34)
    

    and here it is usin lazy-seq directly:

    user>  (defn x [items]
             (when (seq items)
               (lazy-seq (cons (* 2 (reduce + (take-while #(not= :SENTINEL %) items)))
                               (x (rest (drop-while #(not= :SENTINEL %) items)))))))
    #'user/x
    user> (x [1, 5, 2, 5, :SENTINEL, 4, 6, 7])
    (26 34)