clojuresequencecore.asynctransducer

Could core.async have implemented its functions in terms of sequences?


Rich Hickey's Strange Loop transducers presentation tells us that there are two implementations of map in Clojure 1.6, one for sequences in clojure.core and one for channels in core.async.

enter image description here

Now we know that in 1.7 we have transducers, for which a foldr (reduce) function is returned from higher order functions like map and filter when given a function but not a collection.

What I'm trying to articulate and failing, is why core.async functions can't return a sequence, or be Seq-like. I have a feeling that the 'interfaces' (protocols) are different but I can't see how.

Surely if you're taking the first item off a channel then you can represent that as taking the first item off a sequence?

My question is: Could core.async have implemented its functions in terms of sequences?


Solution

  • Yes, in one sense they could have been. If you ignore go blocks (for the moment let's do so), then there's really nothing wrong with something like the following:

    (defn chan-seq [ch]
      (when-some [v (<!! c)]
        (cons v (lazy-seq (chan-seq ch)))))
    

    But notice here the <!! call. This is called "take blocking": inside this function are some promises and locks that will cause the currently executing thread to halt until a value is available on the channel. So this would work fine if you don't mind having a Java thread sitting there doing nothing.

    The idea behind go blocks is to make logical processes much cheaper; to accomplish this, the go block rewrites the body of the block into a series of callbacks that are attached to the channel, so that internally a call to <! inside a go block gets turned into something like this (take! c k) where k is a callback to the rest of the go block.

    Now if we had true continuations, or if the JVM supported lightweight threads, then yes, we could combine go-blocks and blocking takes. But this currently involves either deep bytecode rewriting (like the Pulsar/Quasar project does) or some non-standard JVM feature. Both of those options were ruled out in the creation of core.async in favor of the much simpler to implement (and hopefully much simpler to reason about) local go block transformation.