Hacker News new | ask | show | jobs
by aoeuasdf1 4014 days ago
The way you explain it, it's no different from functions and function composition; in which case, why invent new vocabulary?

I do remember looking into them before and translating them into Haskell and they ended up not being identical to functions in the trivial sense that you suggest, but I forget how.

3 comments

Transducers are functions. The thing is that they are functions that are designed to serve as the functional argument to reduce. And they pair with ordinary functions which are not transducers.

For instance if we have (map inc [1 2]), there exists a transducer function T such that:

  (reduce T [1 2])  ==   (map inc [1 2])
I.e. we can somehow do "map inc" using reduce.

Okay?

Now, the clever thing is this: why don't we allow map to be called without the list argument? Just let it be called like this:

  (map inc)
This looks like partial application, right? Now what would partial application do? It would curry the "inc", returning a function of one argument that takes a list, i.e.:

  ;; if it were partial application, then:
  ((map inc) [1 2])  ;; same as (map inc [1 2])
But Hickey did something clever; he overloaded functions like map so that (map inc) returns T!

  (reduce (map inc) [1 2]) ;; same as (map inc [1 2])
The cool thing is that this (map inc) composes with other functions of its kind. So you can compose together the transducers of list processing operations, which are then put into effect inside a single reduce, and the behavior is like the composition of the original list processors.

It's like a linear operator; like LaPlace. Composition of entire list operations in the regular domain corresponds to composition of operations on individual elements in the "t-domain".

> (reduce (map inc) [1 2]) ;; same as (map inc [1 2])

This is wrong. You've missed the point.

    (map inc [1 2]) 
is actually roughly equivalent to

    (reduce ((map inc) conj) [] [1 2])
which, due to the use of `reduce`, is eager. To get laziness back:

    (sequence (map inc) [1 2])
Transducers are not reducing functions, they return reducing functions when applied to reducing functions. `((map inc) conj)` is a version of `conj` that calls `inc` on all the rhs args before `conj`ing them into the lhs arg.
I suspected I had to be not quite understanding something, because why would we want, say, a map based on reduce that chokes on lazy lists, in a language where laziness is important.
Why have new vocabulary for the state monad?

Transducers are mostly function and function composition, but with a specific signature. There are a handful of contracts on a transducer, so it is useful to have a name, so you can say "this function takes a transducer" and "this function returns a transducer".

Older collection functions were semi-lazy by default. They auto-realized in chunks of 32. Transducers wrap the functions then pass the data through, allowing full-laziness (or not if you want) by default.

They also work well in cases where you don't know if/when a new value is coming, like in channels or observables. This is because they aren't necessarily wed to the seq abstraction from the get-go.