On Thursday, October 16, 2014 1:54:03 AM UTC-4, Michael van Acken wrote:
>
>
>> This is a change I am interested in as well.  Just yesterday I was bitten 
> by the fact that in
> the 3-arity case the init value of transduce defaults to (f) instead of 
> ((xform 
> f)):
>
> While experimenting I was trying to splice a group-by-style transducer 
> into an existing
> (comp ...) chain.  This step works on a map valued accumulator, and needs 
> an initial value
> of {} irregardless of what (f) would provide.
>
> -- Michael
>

Hi Michael,

I’m glad you are in favor of this change; however, and with tongue firmly 
in cheek, you’ve taken a beautiful thing and corrupted it, which I can’t 
condone. ;)

Let me offer an explanation as to why I half-jokingly say this.

>From what you’ve said, your “group-by-style transducer” is going to have 
the following form:

(fn [rf]
  ([] {})
  ...)

Irrespective of the rest of its implementation, it’s a function that 
transforms a reducing function into another reducing function, but it is 
most certainly *not* a transducer—a necessary, but not a sufficient 
condition.

Before I make an appeal to types, let me build some intuition by borrowing 
the analogy that Rich Hickey made in his talk at Strange Loop. He was 
saying that he wanted to give instructions to the baggage handlers about 
how to handle the baggage, without having to say anything about whether the 
baggage arrived on a trolley or by conveyor belt. By returning {} in the 
0-arity, you are effectively saying, “No, it’s definitely going to be on a 
trolley!”.

Transducers have a type. Borrowing the from the exposition in
http://conscientiousprogrammer.com/blog/2014/08/07/understanding-cloure-transducers-through-types/
to a first order of approximation (ignoring initialization, stateful 
transducers, and early termination), but with no loss of generality with 
respect to this discussion, transducers can be typed as follows:

type Reducer a r = r -> a -> r

type Transducer a b = forall r . Reducer a r -> Reducer b r

Your reducing-function transformation effectively has the type:

type SomethingElse a b r = Reducer a r -> Reducer b PersistentMap

No matter what reducing function comes in, a reducing function on 
persistent maps is going to come out the other side.

The `Transducer` type says: for all means of baggage conveyance, you can 
convey me some baggage, and I might transform it in some way, but no matter 
what I give you back, it will be on *exactly* the same means of baggage 
conveyance.

This is *extremely* important, and is the crux of what makes transducers 
great.

You have to promise to give up the right to know the type of the 
reduction—whether you are building a collection with `into`, or reducing 
to, say, a number with `transduce`, or transforming what flows through a 
channel—and in return your transducer can be used to transform values in 
any and all of those processes.

The rank-2 polymorphism used in the type of `Transducer` above expresses 
exactly this promise (the `forall r.` part).

If I call

(into [] xform (range 5))

(transduce xform + 0 (range 5))

I expect a vector and an integer to pop out, respectively, and I’m going to 
be sorely disappointed if a map pops out instead!


By the way, please, please, don’t read this as a polemic. I just want to be 
precise about the issue, and I am certainly sympathetic to “While 
experimenting I…”!


Note the 0-arity of my `init-with` transducer:

(defn init-with
  [x]
  (fn [rf]
    (fn
      ([] (rf (rf) x))
      ...)))

I want to add an input `x`. I need to return some result, but the type of 
the result is unknowable to me. The only way I can construct one of these 
is with `rf`. The 2-arity of `rf` requires a value of this unknowable type 
as a first argument, and again the only way I can construct one of these is 
with `rf` (the 0-arity).

With just `x` and `rf` in scope, there are still an infinite number of 
plausible and valid implementations:

([] (rf))
([] (rf (rf) x))
([] (rf (rf (rf) x) x))
...

etc., etc.

Actually, this is not quite the case. Consider,

([]
   (let [r1 (rf)
         r2 (rf r1 x)]
     (rf r1 x)))

This will satisfy the rank-2 polymorphic type for `Transducer` above, but 
this will *not* lead to a valid transducer.
Here we used `(rf)` to get a seed `r1` to the reduction, we then fed `r1` 
and `x` into `rf` to get `r2`, but then we *discarded* `r2` and fed `r1` a 
*second* time into `rf`. It should be rather obvious that skipping over 
intermediate values of the reduction and letting them float off into the 
ether is incorrect.

To express this property of transducers, one would need not only the rank-2 
polymorphism, but also linear typing
http://en.wikipedia.org/wiki/Substructural_type_system#Linear_type_systems
which would then adequately express that values produced by the reduction 
function supplied to a transducer must be used *exactly once*.


As it stands, the current implementation of the “transducible processes” 
`into`, `transduce`, and `sequence`, all ignore their given transducers 
when constructing the seed value… but that does mean that one can’t corrupt 
the seed value… of course you are still free to return any heinous value 
you like in the other arities if you so choose…


Ok, I better leave it there…

Dan

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to