Transducers: Why left to right rather than right to left?

765 views
Skip to first unread message

Mars0i

unread,
Oct 30, 2014, 11:44:48 AM10/30/14
to clo...@googlegroups.com
Caveat: I am still feeling around in the dark in my understanding of transducers.  What I write below may just convey how clueless I am.

(Meta-caveat: I'm probably spitting into the wind.  I should no doubt use my time more wisely.)


Normal function composition is done starting from the right.  This is familiar from mathematics, other Lisps, most languages, and it's how Clojure's function application and 'comp' work.

Sometimes it's easier to understand composition going from left to right, as in many natural languages and as in unix pipes, and Clojure provides '->' and '->>' to do that.  That's good.  Best of both worlds.  One thing I like about these operators is that their name clearly indicates the direction of function application.

Transducers allow function composition with potential efficiency gains, but apply functions starting from left to right.  But it does this using the name 'comp', which otherwise applies functions from right to left.  What??  Doesn't that seem like a Bad Thing?  Why not use a different name?  (It's like overloading the minus sign so that in some contexts, it subtracts the first argument from the second.)

(Is Clojure is getting too popular?  Its essential features--prefix notation, parentheses, purely functional operations, and laziness--aren't doing enough to scare away Java programmers?  :-)

László Török

unread,
Oct 30, 2014, 11:54:18 AM10/30/14
to clo...@googlegroups.com
Hi,

on transducers generally, watch this https://www.youtube.com/watch?v=6mTbuzafcII .

This part tackles your questions on ordering https://www.youtube.com/watch?v=6mTbuzafcII#t=1531 .



--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
László Török
Checkout justonemorepoint.com - Know your true value

Mars0i

unread,
Oct 30, 2014, 12:31:29 PM10/30/14
to clo...@googlegroups.com
Thanks Las.  That's a verty helpful suggestion, though for me personally it won't help.  (I know that a lot of people like to get information from videos.  I don't.  I'd rather read--then I can use my eyes to find the places that I want to focus on--except when information is better conveyed through motion.  That means that I miss out on some information, sometimes.  Each person has to choose how to use her/his time.)

Mars0i

unread,
Oct 30, 2014, 12:33:59 PM10/30/14
to clo...@googlegroups.com
Sorry--I just realized that you gave me a link to the exact place in the video in which Rich H. talks about order.  Thanks!  That is very helpful.

Jonas

unread,
Oct 30, 2014, 12:52:49 PM10/30/14
to clo...@googlegroups.com
clojure.core/comp has not been changed at all. It's just the nature of how transducers work. Here's a another example where function composition seems to compose left-to-right (the second example, `comp2`):

    (defn add-3 [x] (+ 3 x))
    (defn mul-2 [y] (* 2 y))
    (defn sub-1 [z] (- 1 z))

    (def comp1 (comp add-3 mul-2 sub-1))

    ;; (comp1 2)
    ;; (add-3 (mul-2 (sub-1 2)))
    ;; ((fn [x] (+ x 3)) ((fn [y] (* y 2)) (fn [z] (- 1 z)) 2))
    ;; ((fn [x] (+ x 3)) ((fn [y] (* y 2)) (- 1 2)))
    ;; ((fn [x] (+ x 3)) (* (- 1 2) 2))
    ;; (+ (* (- 1 2) 2) 3)
    ;; 1

    (defn add-3 [f]
      (fn [x]
        (f (+ 3 x))))

    (defn mul-2 [f]
      (fn [x]
        (f (* 2 x))))

    (defn sub-1 [f]
      (fn [x]
        (f (- 1 x))))

    (def comp2 ((comp add-3 mul-2 sub-1) identity))

    ;; (comp2 2)
    ;; ((add-3 (mul-2 (sub-1 identity))) 2)
    ;; ((add-3 (mul-2 (fn [x] (identity (- 1 x))))) 2) 
    ;; ((add-3 (fn [x] ((fn [x] (identity (- 1 x))) (* 2 x)))) 2) 
    ;; ((fn [x] ((fn [x] ((fn [x] (identity (- 1 x))) (* 2 x))) (+ 3 x))) 2)
    ;; ((fn [x] ((fn [x] (identity (- 1 x))) (* 2 x))) (+ 3 2))
    ;; ((fn [x] (identity (- 1 x))) (* 2 (+ 3 2)))
    ;; (identity (- 1 (* 2 (+ 3 2))))
    ;; (- 1 (* 2 (+ 3 2)))
    ;; -9

which is the same as with ->>

    (->> 2 
         (+ 3)
         (* 2)
         (- 1))
    ;; -9

Jozef Wagner

unread,
Oct 30, 2014, 1:59:38 PM10/30/14
to clo...@googlegroups.com

Atamert Ölçgen

unread,
Oct 30, 2014, 10:43:59 PM10/30/14
to clo...@googlegroups.com
We had this exact discussion just yesterday.

I'd suggest forgetting about the technical details, how comp works etc. for a minute. And think about what we're dealing with.

1. If we are dealing with functions, function composition makes sense to be written left to right and applied right to left. If you were working on paper this would feel natural to you. And that's exactly how comp works.

2. When we're dealing with transducers, they are functions under the hood but you know they're not functions that take a value and return another value[1]. Anyway that's an implementation detail. Rich describes transducers as processes, or steps of instructions. Natural way to deal with these things is to write them top to bottom or left to right and then assume they'll be applied in that same order[2].

1: A transducer is a function that takes another transducer or a step function and returns a function that wraps it. If I'm not mistaken second application takes a collection and returns a reducing function on that collection. But I'm probably wrong.

2: How does the order change? I hope this incomplete example helps:

;; applied in reverse order
(defn f [g]
  (fn [coll]
    (g (do-something coll))))


;; f is applied before g, like transducer composition
(comp f g)


;; applied in given order
(defn k [j]
  (fn [coll]
    (do-something (j coll))))

;; j is applied before k, like function composition
(comp k j)




--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Kind Regards,
Atamert Ölçgen

-+-
--+
+++

www.muhuk.com

Mars0i

unread,
Oct 30, 2014, 11:34:08 PM10/30/14
to clo...@googlegroups.com
Jonas, Atamert, thank you.  Very helpful illustrations and comments.
Jozef: A transcribed talk with slides! Excellent.
Las, I might actually break down and watch the talk.  Or parts of it, and read the rest.

Thanks everyone.

Phillip Lord

unread,
Oct 31, 2014, 5:55:39 AM10/31/14
to clo...@googlegroups.com


I want to pass a java method call to a function. So instead of this:


(defn call-method []
(.getCanonicalName Object))


I have something like this...

(defn indirect-call [f clazz]
(f clazz))

(defn indirect-call-memfn []
(indirect-call (memfn ^java.lang.Class getCanonicalName) Object))

(defn indirect-call-anon []
(indirect-call #(.getCanonicalName ^java.lang.Class %) Object))

I am not sure which is faster yet, but either should work. But,
performance-wise I am creating a new function for every call (the
anonymous function) when I only ever need one. Which isn't idea.

One solution would be this:

(def indirect-call-memfn-cached
(let [f (memfn ^java.lang.Class getCanonicalName)]
(fn []
(indirect-call f Object))))

(def indirect-call-anon-cached
(let [f #(.getCanonicalName ^java.lang.Class %)]
(fn []
(indirect-call f Object))))


Or, if I want to stay using defn the slightly evil looking:

(let [f #(.getCanonicalName ^java.lang.Class %)]
(defn indirect-call-defn-anon-cached[]
(indirect-call f Object)))


Which seems to work, but is fairly horrible syntactically.

I have thought about writing a macro which I might call "one-fn" which
looks like fn but caches. The best I have come up with is to turn the
contents of the &form variable into a hash, and store the functions in
global state. So something like this...

(one-fn [x] (.getCanonicalName x))


At run time we would have something like

(if-let [f (get &one-fn-state 423432423)]
f
(let [r (fn [x] (.getCanonicalName x))]
(swap! one-fn-state assoc 423432423 r)
r))


I can see some nasty edge cases where this will break, of course. But
most of all, I wonder whether I am missing something obvious that would
be far better. In the ideal world, I'd like one-fn to affect the
compiled code, so that a single instance is shared, but I can't see a
way to achieve this.

Thoughts? Has it already been done?

Phil

Mars0i

unread,
Oct 31, 2014, 8:54:11 AM10/31/14
to clo...@googlegroups.com
Phil, I think your post accidentally ended up in the wrong place.  I believe you intended to create a new thread.


On Friday, October 31, 2014 4:55:39 AM UTC-5, Phillip Lord wrote:


I want to pass a java method call to a function. So instead of this:
...

Phillip Lord

unread,
Oct 31, 2014, 8:58:25 AM10/31/14
to clo...@googlegroups.com

Oh, dear, did I leave a trailing reference in my headers?
--
Phillip Lord, Phone: +44 (0) 191 222 7827
Lecturer in Bioinformatics, Email: philli...@newcastle.ac.uk
School of Computing Science, http://homepages.cs.ncl.ac.uk/phillip.lord
Room 914 Claremont Tower, skype: russet_apples
Newcastle University, twitter: phillord
NE1 7RU
Reply all
Reply to author
Forward
0 new messages