By definition, a lazy sequence generates/computes its elements on demand, as they are requested. With that in mind, it helps to think through the computing steps required when a single element - e.g. the first one - is requested:
- Within
slow-consumer
's keep
's argument function, an element from the enclosing input is requested in order to print it out
- That element is taken from the output of
fast-producer
, which has produced none yet
- Within
fast-producer
, the function within map is invoked on the next element, prints it and returns it
- Finally,
slow-consumer
's keep
's arg. fn has something to chomp on, prints the value, then calls Thread/sleep
-
Thread/sleep
always returns nil
, and therefore nil
is returned from keep
's argument fn
- As an element is being requested, but none has been found so far (
keep
is not allowed to return nil
values, see also previous step), keep
invokes the argument function on its next input element (the next element output by fast-producer), etc
So to answer your original question, the consumer and producer values alternate because the consumer takes its values from a lazy sequence (the producer), whose values are generated on demand. At the (first) time slow-consumer requests an element from its input, only that element is made available by its input collection (since it is generated on demand by fast-producer), so there is no context switching as you suggested, but just a single value output by the first function (fast-producer) into the client function requesting it (the slow-consumer).
However there is a second issue in your specific example, due to a lazy sequence’s obligation to provide an element, even when none is available (step 6 above), which may have added to your initial confusion.
We can illustrate the above by playing around a bit with your example and separating the phases of the pipeline:
(def output (slow-consumer (fast-producer 5)))
;; #'user/output
(first output)
;; produce 4
;; consume 4
;; produce 3
;; …
(def from-producer-only (fast-producer 5))
;; #'user/from-producer-only
(first from-producer-only)
;; produce 4
;; 4
;;user>
(def from-consumer-only (slow-consumer (reverse (range 5))))
;; #'user/from-consumer-only
(first from-consumer-only)
;; consume 4
;; consume 3
;; consume 2
;; …
One more thing about lazy sequences worth noting however: the side-effects happen only once, i.e. when the computation occurs. All subsequent requests for an element come from memory:
(first from-producer-only)
;;4
And, as a direct consequence of that, all retained elements consume space, which can become a problem if the lazy sequence is large and is referenced for a long time (e.g. by a var).