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