Paul Chiusano and Rúnar Óli have written a fantastic book Functional programming in Scala. In it they mention a little-referenced concept in the Scala community - Transducers.
In the Clojure Community - Transducers get a little more press.
My question is: What are the similarities and differences between Scala Transducers **(from the book Functional Programming in Scala) and Clojure Transducers?**
Assumptions:
I'm aware that
Transducers are common parlance from their concept in Electrical Engineering
There is a pre-existing concept in Computer Science called a Finite State Transducer
There is a precedent in Biology and Psychology adopting the word transduction
There is already a history of other technical books like SICP adopting the word Transducers.
The stream transducers from the book Function Programming in Scala (FPiS) and Clojure's transducers are quite similar. They are a generalisation of the idea of having a "machine" (step function) to process the input stream into the output stream. FPiS's transducers are called
Process
es. Rich Hickey also uses the term process in his introductory talk on transducers in Clojure.Origins
The design of FPiS's transducers is based on Mealy machines. Mealy machines are said to have:
These functions can be fused together to form:
Heres it's easy to see that the step function operates on the current state of the machine and the next input item to produce the next state of the machine and output item.
One of the combinators from FPiS uses such a step function:
This
loop
function is essentially the seeded left reduction that Rickey talks about in this slide.Context agnostic
Both can be used in many different context (such as lists, streams, channels, etc.).
In FPiS transducers, a process type is:
All it knows about are it's input elements and it's output elements.
In Clojure, it's a similar story. Hickey calls this "fully decoupled".
Composition
Both types of transducers can be composed.
FPiS uses a "pipe" operator
Clojure uses
comp
Representation
In Clojure:
In FPiS:
However, FPiS's representation isn't just a function under the hood. It's a case-class (algebraic data type) with 3 variants: Await, Emit, and Halt.
reduced
in Clojure.Early termination
Both support early termination. Clojure does it using a special value called
reduced
which can be tested for via thereduced?
predicate.FPiS uses a more statically typed approach, a Process can be in one of 3 states: Await, Emit or Halt. When a "step function" returns a process of state Halt, the processing function knows to stop.
Efficiency
In some points they are again similar. Both types of transducers are demand-driven and don't generate intermediate collections. However, I'd imagine that FPiS's transducers are not as efficient when pipelined/composed as the internal representation is more than "just a stack of function calls" as Hickey puts it. I'm only guessing here about the efficiency/performance though.
Look into
fs2
(previouslyscalaz-stream
) for a perhaps more performant library that is based on the design of the transducers in FPiS.Example
Here's an example of
filter
in both implementations:Clojure, from Hickey's talk slides:
In FPiS, here's one way to implement it:
As you can see,
filter
is built up here from other combinators such asawait
andemit
.Safety
There are a number of places where you have to be careful when implementing Clojure transducers. This seems to be a design trade-off favouring efficiency. However, this downside would seem to effect mostly library producers rather than end-users/consumers.
reduced
value from a nested step call, it must never call that step function again with input.The transducer design from FPiS favours correctness and ease of use. The pipe composition and
flatMap
operations ensure that completion actions occur promptly and that errors are handled appropriately. These concerns are not a burden to implementors of transducers. That said, I imagine that the library may not be as efficient as the Clojure one.Summary
Both Clojure and FPiS transducers have:
They differ somewhat in their underlying representation. Clojure style transducers seem to favour efficiency whereas FPiS transducers favour correctness and compositionality.