Live data from Hacker News

Transducers are coming to Clojure

blog.cognitect.com

51–60 of 103 posts

Re: Transducers are coming to Clojure

#51
post #14

Earlier quoted context omitted.

Providing the signature of this new `map` function (e.g. as in Haskell's fmal http://www.haskell.org/hoogle/?hoogle=fmap ), would certainly go a long way towards helping people understand what this `map` does.

These sigs are for the arities below only. map f: (a->b)->(x->b->x)->(x->a->x) filter pred: (a->bool)->(x->a->x)->(x->a->x) flatmap f: (a->[b])->(x->b->x)->(x->a->x) etc.

So, a transduceMap implementation would be this I guess?

  transduceMap :: (b -> a) -> (acc -> a -> acc) -> (acc -> b -> acc)
  transduceMap f = \reduceFn -> \acc el -> reduceFn acc (f el)
lambda added for clarity (no pun intended), however types are easier to match when using this syntax:

  transduceMap :: (b -> a) -> (acc -> a -> acc) -> acc -> b -> acc
  transduceMap f reduceFn acc el = reduceFn acc (f el)

Re: Transducers are coming to Clojure

#52
post #13

This sort of reminds me of the Church-encoded form of a list. newtype Fold a = Fold (forall r . (a -> r -> r) -> r -> r) fold :: [a] -> Fold a fold xs = Fold (spin xs) where spin [] cons nil = nil spin (a:as) cons nil = cons a (spin as cons nil) refold :: Fold a -> [a] refold (Fold f) = f (:) [] Notably, since `fold` and `refold` are isomorphisms then we can do everything we can do to `[a]` to `Fold a` map :: (a -> b…

Kind of. The idea is to get out of the context of the 'whole job' (the ->r->r bit above) and focus on transformations of the step function (a->r->r) -> (b->r->r) {using your arg order above}. Not talking about the whole job (i.e. the source and result) makes for much more highly reusable components, especially when the jobs don't produce concrete results but, e.g., run indefinitely, like channel transformations.

Yeah, I'm less sure about the properties as you go this way. You ought to be able to get an Arrow out of it and it's a pretty natural idea.

Re: Transducers are coming to Clojure

#53
post #13

This sort of reminds me of the Church-encoded form of a list. newtype Fold a = Fold (forall r . (a -> r -> r) -> r -> r) fold :: [a] -> Fold a fold xs = Fold (spin xs) where spin [] cons nil = nil spin (a:as) cons nil = cons a (spin as cons nil) refold :: Fold a -> [a] refold (Fold f) = f (:) [] Notably, since `fold` and `refold` are isomorphisms then we can do everything we can do to `[a]` to `Fold a` map :: (a -> b…

For those curious a about this, look up Haskell's `build` and `destroy` functions. Those functions are for church-encoding lists and doing optimizations that way.

Re: Transducers are coming to Clojure

#54
post #14

Earlier quoted context omitted.

Providing the signature of this new `map` function (e.g. as in Haskell's fmal http://www.haskell.org/hoogle/?hoogle=fmap ), would certainly go a long way towards helping people understand what this `map` does.

These sigs are for the arities below only. map f: (a->b)->(x->b->x)->(x->a->x) filter pred: (a->bool)->(x->a->x)->(x->a->x) flatmap f: (a->[b])->(x->b->x)->(x->a->x) etc.

OK, maybe we're getting somewhere. Let me try to write this `map`, just to see. I'm using Scala, so I can put some types, and have the compiler yell at me if I'm doing something overtly wrong (I need all the help I can get!).

For clarity, let's define a type alias for reducers:

    type Reducer[X, A] = (X, A) ⇒ X
Let's define `map` to match the type definition you provided. And with that type definition, I only see one way in which the function can be implemented. So it must be:

    def map[X, A, B](f: A ⇒ B): (Reducer[X, B] ⇒ Reducer[X, A]) =
        (redB: Reducer[X, B]) ⇒ (x: X, a: A) ⇒ redB(x, f(a))
How can I use this? Let's try the following:

    def addup(zero: Int, a: List[Int]) = a.foldLeft(zero)(_ + _)
    def parseList(a: List[String]) = a.map(_.toInt)

    map(parseList)(addup)(1, List("7", "8"))
This returns 16. OK, parsing the list, and adding up starting from 1. But it doesn't look to me like `map` implements anything like the usual semantic of map. It just converts the data structure, and applies the reducer. What am I missing here?

Re: Transducers are coming to Clojure

#55
post #45

I'm not quite sure what this means, so here's my attempt to translate this into Python. A reducer is a function such as `add`: def add(sum, num): return sum + num Of course you can plug `add` directly in `reduce(add, [1, 2, 3], 0)` which gives `6`. A transducer is an object returned by a call such as `map(lambda x: x + 1)`. You can now apply the transducer to a reducer and get another reducer. map_inc = map(lambda x:…

I'm also hoping for an informative answer to this question. Anyone?

Re: Transducers are coming to Clojure

#56
post #45

I'm not quite sure what this means, so here's my attempt to translate this into Python. A reducer is a function such as `add`: def add(sum, num): return sum + num Of course you can plug `add` directly in `reduce(add, [1, 2, 3], 0)` which gives `6`. A transducer is an object returned by a call such as `map(lambda x: x + 1)`. You can now apply the transducer to a reducer and get another reducer. map_inc = map(lambda x:…

I'm also hoping for an informative answer to this question. Anyone?

I think its just reduce, but in general its possible to write many implementations of reduce. It could be applied to lazy sequences, observables (to get a future or a new observable) and many other reducible things

Re: Transducers are coming to Clojure

#57
I think that a good way to understand transducers is to look at their implementation (shortened a bit). Here it is for map:

    ([f]
    (fn [f1]
      (fn
        ([result input]
           (f1 result (f input)))
        ([result input & inputs]
           (f1 result (apply f input inputs))))))
filter:

    ([pred]
    (fn [f1]
      (fn
        ([result input]
           (if (pred input)
             (f1 result input)
             result)))))
And it gets more interesting with take:

    ([n]
     (fn [f1]
       (let [na (atom n)]
         (fn
           ([result input]
              (let [n @na
                    nn (swap! na dec)
                    result (if (pos? n)
                             (f1 result input)
                             result)]
                (if (not (pos? nn))
                  (reduced result) ; a terminal value indicating "don't reduce further"
                  result)))))))
The transducer is supplied with the reducer next in the chain (f1) and returns a reducer function that gets fed with the reduced value by the preceding reduction (result) and the next element (input). Note how the take transducer maintains internal state with an atom. This could get a little tricky for more elaborate reductions, as how the internal state is maintained might have a significant effect on performance, depending on exactly how the reduction is performed. For example, if the reduction is done in parallel (say, with fork-join), then an internal state that's updated with locks (like refs) might significantly slow down -- or even deadlock -- the reduction.

AFAICT mapcat still only returns lazy-seqs.

Re: Transducers are coming to Clojure

#58
post #56

Earlier quoted context omitted.

I'm also hoping for an informative answer to this question. Anyone?

I think its just reduce, but in general its possible to write many implementations of reduce. It could be applied to lazy sequences, observables (to get a future or a new observable) and many other reducible things

Yes, it's not just the 'reduce' function. You can think of many kinds of jobs in terms of seeded left reductions. Here's an example of some of the functions that can apply a transducer to their internal 'step' operation:

https://gist.github.com/richhickey/b5aefa622180681e1c81

Note how one transducer stack is created and can be reused in many different contexts.

Re: Transducers are coming to Clojure

#59
post #57

I think that a good way to understand transducers is to look at their implementation (shortened a bit). Here it is for map: ([f] (fn [f1] (fn ([result input] (f1 result (f input))) ([result input & inputs] (f1 result (apply f input inputs)))))) filter: ([pred] (fn [f1] (fn ([result input] (if (pred input) (f1 result input) result))))) And it gets more interesting with take: ([n] (fn [f1] (let [na (atom n)] (fn ([resu…

Because mapcat's signature was not amenable to the additional arity, there's now also flatmap (note you can write the lazy collection version of any transducer fn using sequence as below):

    (defn flatmap
      "maps f over coll and concatenates the results.  Thus function f
      should return a collection.  Returns a transducer when no collection
      is provided."
      ([f]
       (fn [f1]
         (fn
           ([] (f1))
           ([result] (f1 result))
           ([result input]
              (reduce (preserving-reduced f1) result (f input))))))
    
      ([f coll] (sequence (flatmap f) coll)))

Re: Transducers are coming to Clojure

#60
post #38
post #18

Earlier quoted context omitted.

It's not a formal notation. It's talking about a pattern in function signature. The function takes in some parameters (whatever, input) and spits out an output ( -> whatever ). The "reducing function signature" basically is just the function signature of a "reducer" (or "fold") function in the map/reduce (or map/fold) pattern. The "whatever" is kind of sloppy and confusing. It's the accumulating memo parameter of a r…

Thanks. I just looked up "map/reduce" on Wikipedia. Gee, I've been speaking 'prose' (an old joke) all along! So, I was procrastinating from working on my code, and there I have some data base data split, for some positive integer n, into n 'partitions'. The intention, later, is to use n servers, one server for one partition. Then I have some data X to be 'applied' to all the data in all the partitions, and from each…

Actually "transducer" can be done with straight function composition. It would work in any language supporting high order function, a fancy way of saying passing function around as argument or return value.

e.g. in Javascript (I'll be overly verbose for illustration)

    function mySumReducer(sum, value1) { 
        return sum + value1;
    }
    function myTimesReducer(product, value1) { 
        return product * value1;
    }

    [1, 2, 3, 4].reduce(mySumReducer, 0) gives 10
    [1, 2, 3, 4].reduce(myTimesReducer, 1) gives 24


    function myDoubler(x) {
        return x * 2;
    }

    function valueTransducer(originalReducer, valueEnhancer) {
        var newReducer = function(memo, value) {
            var newMemo = originalReducer(memo, valueEnhancer(value));
            return newMemo;
        }
        return newReducer;
    }

    var myDoubleSumReducer = valueTransducer(mySumReducer, myDoubler);
    var myDoubleTimesReducer = valueTransducer(myTimesReducer, myDoubler);

    [1, 2, 3, 4].reduce(myDoubleSumReducer, 0) gives 19
    [1, 2, 3, 4].reduce(myDoubleTimesReducer, 1) gives 192
valueTransducer is a generic transducer that can be used to apply an extra function to the value during the reduction process. Voila, you got a transducer in Javascript!

To make it more useful,

    function fancyTransducer(originalReducer, valueEnhancer, memoEnhancer) {
        return function(memo, value) {
            return memoEnhancer(originalReducer(memo, valueEnhancer(value)));
        }
    }
This generic transducer can transform the value and memo of the original reducer. Also since the transducer returns another reducer, you can chain it up by calling transducer again with it using different enhancers. The wonder of functional composition.

It's nothing fancy once it's laid out. It's just a useful programming pattern.

Post reply on HN