News for hackers. Stuff that matters.
Re:“Transducers” by Rich Hickey at Strange Loop [video] by tel on Saturday September 20, 2014 @01:00AM (#8343726)
First, to be clear, I really liked this presentation. The criticism below is both technical and small---all in all I greatly enjoy Rich Hickey's work and generally admire his ability to talk compellingly about complex technical topics.

That said.

I somewhat disliked Hickey's presentation of typing transducers here. I feel as though he builds a number of strawmen toward typing and then tries to knock them down and suggest that either Clojure has some kind of mystical mechanism that is ineffable to types or that the exercise of typing transducers is wasteful. I disagree on both accounts, I suppose. I think types are useful for analysis and teaching.

The two major points he seems to make is that in order to "properly type" transducers you must

    1. Index the type of the "accumulation so far" so 
       that it cannot be transformed out-of-order
    2. Implement early stopping "without wrapping anything
       except for the reduced value"
There may be other critiques as well, but I want to examine these two in the context of Haskell.

With respect to the first point, the major concern appears to be prohibiting behavior loosely described as "applying the reducing function, say, 3 times and then returning the first resulting accumulation". In some sense, the idea is to force us to be faithful in passing on the accumulating parameter. In code, a pathological setting is the following:

    transduce :: (r -> a -> r) -> (r -> a -> r)
    transduce reduce accu0 a = 
      let acc1 = reduce acc0 a
          acc2 = reduce acc1 a
          acc3 = reduce acc2 a
      in  acc1
The concern is unfounded in a pure language, however, since calling `reduce` can have no side effects. This entails that all possible effects on the world of calling `reduce` are encapsulated in the return and, therefore, we can completely eliminate the steps producing `acc2` and `acc3` without worry.

    transduce :: (r -> a -> r) -> (r -> a -> r)
    transduce reduce accu0 a = 
      let acc1 = reduce acc0 a
      in  acc1
Now, there may be concern here that we still want to index the `r` type somehow to allow for changes of accumulation to occur. This is not the case (in this simple model!) as in order to achieve the "baggage carrier independence" property the `r` type must be left unspecified until the transducer is actually applied. The cleanest way to do that is to use a higher-rank type (Hickey mentions these briefly and offhandedly toward the end of his talk)

    type Transducer a b = forall r . (r -> b -> r) -> (r -> a -> r)
which thus prohibits the implementer of a Transducer from affecting the values of `r` in any way whatsoever---they must be left anonymous until someone decides to use the Tranducer on a particular collection of values `a`.

(It must be noted that the model given above is isomorphic to a "Kleisli arrow on the list monad" which I described a little bit here http://jspha.com/posts/typing-transducers/. It should also be noted that this model includes neither (a) the ability to use local state to capture time-varying transductions or (b) the ability to terminate early)

With respect to the second point, I'd like to suggest that there is a difference between the semantic weight of wrapping the result types in an Either in order to indicate early termination and the implementation weight. I completely agree that using an Either to implement early stopping (as it's easy, if finicky for the library implementor, to do) will involve wrapping and unwrapping the "state" of the transduction continuously. I also would like to suggest that it's a very natural way of representing the "accumulation | reduction" notion Hickey uses in his own "Omnigraffle 8000 type system".

We really would like to capture the idea of the transducer state as being "either" in-progress or fully-reduced and act accordingly. If Clojure's implementation of that requires fewer runtime tags than an Either, so be it, but I personally fail to see a semantic difference except in the way one can play fast-and-loose with dynamic types over static types to begin with.

---

So, I gave above an implementation of Transducers in types which has some of their properties, but certainly not all. In fact, I abused the fact that there is no ambient state in Haskell in order to ensure that a certain property would hold (notably this doesn't require a type system at all, just purity). I also argued that using Either is a perfectly natural way to implement early termination in such a transduction pipeline.

I've also made an extension to the `(r->b->r) -> (r->a->r)` mechanism which enables local state to be enabled for various components of the transduction pipeline. A version without early termination is available here:

https://gist.github.com/tel/714a5ea2e015d918f135

Notably, this uses most of the same typing tricks as `(r->b->r) -> (r->a->r)` but adds a "reduction local hidden state" variable which lets us implement `take` and `partition`. This takes Hickey's notion of needing to be explicit about the state being used to a whole new level.

---

So what is the point of all this?

I'd like to argue that Transducers do not present such a mysterious mechanism that they cannot be sanely typed in a reasonably rich language. I believe that I can capture most of their salient features in types without using the dependent indexing Hickey suggested was necessary.

More than this, the compartmentalized, hidden reducer-local state in the Gist implementation allows for each reduction step to include fairly exotic local states in their state machine. You could implement a kind of type indexing here if desired and no end-user would ever know of its existence.

I also absolutely concede that many type systems people regularly use could not achieve this kind of encoding.

Finally, what I really want to say is that type systems are not something to be denigrated. I believe some of the earliest "transducers v. types" argumentation took a nasty turn as amateur type theorists (myself included) rushed to write things like "Transducers are just X".

I want to apologize for any kind of bad feelings my own writing in that thread may have stirred up. I try not to be haughty or dismissive with this kind of writing, but I also make mistakes.

So what I'd really like to suggest is that types should not be taken as reductivist on interesting techniques like Transducers but instead as a tool for analyzing their construction and either improving it or better teaching it. Hickey himself often turns to some kind of "pseudotyping" to talk about how Transducers work---formalizing those notions should only lead to greater clarity.

Of course, implementations will differ in small ways. As I've noted abundantly here, a major difference between the Haskell and Clojure implementations is driven more by Haskell's purity than its typing. Hopefully, however, exploration of alternative implementations and the rich analysis produced by their typing can help to introduce new ideas.

For instance, the Gist implementation, if you strip the types away, is an interesting divergence in raw functionality from Clojure Transducers---if Clojure Transducers are "reduction function transformers" than the Gisted Transducers are "Moore-style (Infinite) State Machine transformers" and that difference allows the implementer to be extra explicit about the use of local state.

I'd rather see discussion about whether such InFSM transformation techniques have a place in Transducers literature than a fight over whether or not its possible or reasonable to "type transducers".

Re:“Transducers” by Rich Hickey at Strange Loop [video] by dwenzek on Monday September 22, 2014 @05:10PM (#8352669)
I don't understand why you use a "Moore-style State Machine". I don't understand too why all the typed transducers which are shown here use a restricted version of reducers (i.e. a step function of type `r -> a -> r`). Rich Hickey points out that transducers must deal with initialization and completion. At minute 42:26 of his talk, he even presents a complete picture of tranducers type. They are reducer transformers where a reducer is rather made of 3 pieces :

   data Reducer a r s = Reducer {
      init :: () -> r,
      step :: r -> a -> r,
      complete :: r -> s
   }
We may than write :

   type Transducer a b = (Reducer a r s) -> (Reducer b r s)
But I think that we miss the point doing that. A transducer should be able to lead to a new reducer which use a different type to accumulate inputs. This opens the way to state management and the implementation of transducer like `taking` or `partition`.

Leaving Haskell for OCaml I'm more comfortable with :

   type ('a, 'b, 'c) red = {
      init : unit -> 'b;
      step : 'b -> 'a -> 'b;
      comp : 'b -> 'c;
   }

   let taking n reducer =
     let init () = (0,reducer.init ()) in
     let step (i,acc) item = if (i<n) then (i+1,reducer.step acc item) else (n,acc) in
     let comp (i,acc) = reducer.comp acc in
    {
       init = init;
       step = step;
       comp = comp
    }
The type of taking is `int -> ('a, 'b, 'c) red -> ('a, int * 'b, 'c) red`. The former `b` accumulator state has been replaced by a pair `(int,b)` where a counter is used to track how much items have been taken so far. Yes this should be improved with early termination. But the key point I want to highlight is that wrapping all the transducers into a single general type may imply the use of some sort of existential type (to abstract away the accumulation type). This general type is furthermore useless for the system to check the compatibility of a reducers along a transducer chain.

---

Another point I would like to say is that if transducers are undoubtedly an important and effective mean to build efficient transformation chains, they doesn't deserve to be pushed forward of conventional transformers (map, filter, take, ...) to which we are used for long.

Based on collection definition as something whose content is reducible, we can define the 'map' collection transformation on the basis of 'mapping' reducer transformation

  type 'a col = {
    fold: 'b 'c. ('a, 'b, 'c) red -> 'c
  }
   
  let map f col =
  {
    fold = fun reducer ->  col.fold (mapping f reducer)
  }
So, we have the clarity of map/filter/reduce with the power of transducers.

https://github.com/didier-wenzek/odist is a project where I started to implement these ideas some months ago. This transducer discussion will undoubtedly energize me to work again on the topic !

Re:“Transducers” by Rich Hickey at Strange Loop [video] by dragonwriter on Monday September 22, 2014 @05:26PM (#8352747)
> But I think that we miss the point doing that. A transducer should be able to lead to a new reducer which use a different type to accumulate inputs.

So, instead of

  type Transducer a b = (Reducer a r s) -> (Reducer b r s)
you have:

  type Transducer a b r r' = (Reducer a r s) -> (Reducer b r' s)
Or, most generally (adding in the ability to change not only the type of the accumulator but also the type of the final result):

  type Transducer a b r r' s s' = (Reducer a r s) -> (Reducer b r' s')
Re:“Transducers” by Rich Hickey at Strange Loop [video] by lomnakkus on Saturday September 20, 2014 @02:39AM (#8343862)
Thanks for that excellent comment.

I must say I haven't watched the presentation (yet!), but your comment touches on something that really irks me when people who mostly use unityped languages comment on staticially type-checked languages. It very often turns into this weird thing where you just hear complaining about "types getting in your way", whereas I personally tend to see it as "the types are there to help you understand what you're actually doing". Maybe it stems from a difference in approach. I'll usually practice what's been called "typeful programming" where I just write all the type declarations for data/functions and use "undefined" ("???" for Scala folks) as the implementation just to see if the whole thing fits together before trying to code up any of the details. The types often lead to an "obvious" implementation of any given function. This is very similar to top-down programming. It seems to me that most "dynamic" programmers (esp. of Lisp-derived languages) tend to do things bottom-up. I think this may be at the root of a sizable portion of the "gets-in-the-way"/"there to help you" schism.

Re:“Transducers” by Rich Hickey at Strange Loop [video] by ufo on Saturday September 20, 2014 @01:51PM (#8345090)
I'm not sure using Either to implement early stopping is that different from what Clojure is doing. Since its a dynamically typed language, all objects are already wrapped and tagged so they can use the "Reduced" tag to stand for Left and anything else to stand for Right. This is the same idea of using null to stand for Nothing and anything else to stand for Just.

Other than that, I assume that it should be possible to switch to some continuation-passing implementation if you really wanted to avoid using Either. I probably won't matter much unless you have deeply nested transducers though.

Re:“Transducers” by Rich Hickey at Strange Loop [video] by tel on Saturday September 20, 2014 @02:11PM (#8345147)
I'm personally not really sure there's a difference either, but I wanted to be conservative in what I argued. Even if Clojure is more efficient, the semantics are still identical.
Re:“Transducers” by Rich Hickey at Strange Loop [video] by nickik on Saturday September 20, 2014 @07:57AM (#8344315)
> higher-rank type

Im not a type person. So here is my question, how many languages other then haskell have this?

Re:“Transducers” by Rich Hickey at Strange Loop [video] by ufo on Saturday September 20, 2014 @01:41PM (#8345058)
Any dynamic language should do, kind of :)

For example, the following code needs Higher Ranked Types to typecheck:

    id :: forall t. t -> t
    id x = x

    -- mk_pair's type is a rank-2 type, because its
    -- f parameter is a polymorphic function that gets applied
    -- to different types inside mk_pair.
    mk_pair :: (forall t. t -> t) -> (Int, String)
    mk_pair f = (f 10, f "str")

    a_pair :: (Int, String)
    a_pair = mk_pair id
But it can obviously be written in an untyped language if you wanted to. Just erase all those type annotations:

    function id(x){ return x}

    function mk_pair(f){ return [f(10), f("hello")] }

    var a_pair = mk_pair(id);
The deal with higher-ranked types is basically a tradeoff between the flexibility of the type system and the power of type inference. If you restrict yourself to rank-1 types (that is, none of your function arguments are polymorphic) then the compiler can infer all all types in the program without you having to write any type signatures. If you want to use higher ranked types then the compiler can't infer everything anymore so you might need to add some type annotations yourself.
Re:“Transducers” by Rich Hickey at Strange Loop [video] by tel on Saturday September 20, 2014 @02:09PM (#8345141)
The "kind of" being really pertinent. In particular, this forgoes the guarantee that the only thing the transducer is allowed to do is return a value from the reduction. Hickey notes that this is a law of writing a proper transducer. Higher rank types ensure that this law is properly encoded directly into the system.

That said, I'm pretty sure you know this :)

Re:“Transducers” by Rich Hickey at Strange Loop [video] by dwenzek on Monday September 22, 2014 @01:29PM (#8351510)
To code typed transducers we only need "for all" generics.

   type Transducer a b = forall r . (r -> b -> r) -> (r -> a -> r)
This may seems unusual or complex but is available in mainstream languages. In java:

   interface Transducer<A,B> {
      <R> Reducer<B,R> transduce(Reducer<A,R> reducer);
   }
See https://gist.github.com/didier-wenzek/07faa3990ff03fdea372 for a more complete java implementation of transducers.
Re:“Transducers” by Rich Hickey at Strange Loop [video] by noelwelsh on Saturday September 20, 2014 @08:59AM (#8344392)
Re:“Transducers” by Rich Hickey at Strange Loop [video] by tel on Saturday September 20, 2014 @10:24AM (#8344552)
I'm not sure offhand. Even if your language lacks it, however, you can usually get away without it. The following works

    Transducer r a b = (r -> b -> r) -> (r -> a -> r)
but if you even accidentally specialize the `r` while constructing Transducers then you will have reduced their ability to generalize over input and output containers. In other words, it'll be up to you to maintain the universality of `r`.
Re:“Transducers” by Rich Hickey at Strange Loop [video] by kvb on Sunday September 21, 2014 @09:09AM (#8347054)
You can easily do this even in a language like C#, it's just that the syntactic overhead can be a bit much:

    interface Transducer<A,B> {
        Func<R,B,R> Apply<R>(Func<R,A,R> f);
    }
Re:“Transducers” by Rich Hickey at Strange Loop [video] by tel on Sunday September 21, 2014 @09:42AM (#8347138)
That's awesome! I wasn't aware C# had them.
Re:“Transducers” by Rich Hickey at Strange Loop [video] by bmurphy1976 on Wednesday October 01, 2014 @04:31PM (#8397514)
I took this as a challenge. I wasn't entirely confident with my understanding, so I created a fully realized version in C# available here: https://github.com/bmurphy1976/transducers-dotnet
Re:“Transducers” by Rich Hickey at Strange Loop [video] by kvb on Sunday September 21, 2014 @02:15PM (#8347820)
Yes, higher rank types are no problem for C# or Java generics. It's higher-kinder types that are unavailable.