Reducers, transducers and core.async in Clojure
eli.thegreenplace.net
eli.thegreenplace.net
That being said, it really bothers me when functional programming evangelists write out such horrible Python code examples. It makes Python look like it's not a functional language. It perpetuates the idea that Python won't let you write elegant and stateless programs that behave in a functional manner.
If the author had not mentioned Python and had instead used it as pseudocode to represent the entirely non-functional way to write something, I would have been okay with it. But calling out Python specifically is just incorrect!
I have rewritten the code in a very pythonic manner that illustrates the functional capabilities of the language:
even = lambda x: x % 2 == 0
# alternatively, depending on your religious beliefs:
def even(x): return x % 2 == 0
def process(seq):
return sum(x + 1 for x in seq if even(x))
Note the (parens) for the comprehension instead of [brackets], which creates a generator. The above code is lazy (in Python 3)!And if we fire up a python repl and play around:
In [2]: process(range(10))
Out[2]: 25
I really love Clojure and use it daily, but I find the Python version to be far more legible. It reads more like english.Anyway, the moral of the story here is that you can do immutable/functional programming with regular ol' Python.
It's a comprehension.
The other point in the article was that your process has now hard-coded even and sum, and composing them in python is a bit unwieldy.
I've run into a number of cases where it's just easier in python to write out the loops than string comprehensions or map/filter/reduces together because there's no threading macro or haskell's functional apply operator.
def even(x):
return x % 2 == 0
def inc(x):
return x + 1
def process(seq):
return sum(map(inc, filter(even, seq))) process = pipeline(partial(filter, even),
partial(map, inc),
sum)
process(some-iterable)
I'll grant that it's not syntactically ideal (this is the tradeoff the thrush combinator (pipeline) running as a function instead of working as a macro. The benefit of being able to follow down the program as it works remains. (def process [xs]
(->> xs
(filter even?)
(map inc)
(reduce +)))
(process some-iterable)
If you compare a function-based approach[1] (instead of a macro) you might get: (def process
(pipeline
(partial filter even?)
(partial map inc)
(partial reduce +)))
process(some-iterable)
[0] A sample Python definiton (python doesn't have compose to reverse): def pipeline(*fns):
return reduce(lambda f, g: lambda *xs, **ys: g(f(*xs, **ys)), fns)
[1] A clojure implementation: (defn pipeline [& fs] (apply compose (reverse fs))) def even(x): return x % 2 == 0 formulas = {
'sum': lambda x,y: x+y,
'subst': lambda x,y: x-y,
'mult': lambda x,y: x*y,
'pow': lambda x,y: x**y
}
def apply_operator(operation_name, x, y):
operation=formulas[operation_name]
return operation(x,y)
Using "def"s there would kill readability and easiness of coding.Most simple uses of lambda like this can be substituted with functions from the operator module:
import operator
formulas = {
'sum': operator.add,
'subst': operator.sub,
'mult': operator.mul,
'pow': operator.pow
}However, what I meant was that for fast, quick coding, if your 'operator' function is really simple, then lambda fints perfectlu.
I know the Zen of Python says: "There should be one—and preferably only one—obvious way to do it", but i don't align to that principle. I think there should be more than one way to do something, and one should choose one that fits the best.
I'm happy for you to use lambdas in an expression context, if that's how you like to roll, but assignment is a statement anyway, so it doesn't matter there and you might as well choose the one that produces helpful tracebacks.
def even(x): return not(x % 2)I suspect that it will be easier for core Python developers rather that general Python programmers because they will be more intimately familiar with the conversion rules. I would be more comfortable with such a construct in C or C++, because I am more confident in those conversion rules. But, even though Python's are quite similar, I had to check myself before making my comment, because I knew they were similar, but I was not sure what they were. The situation in the stackoverflow comment is also not quite the same: asking a question about the relationship of numbers (is x modded by 2 equal to zero) is a special case of using integers in a boolean context. Saying it's Pythonic to use an integer in a boolean context does not necessarily mean it's best to always do so.
I don't feel that there is anything incidental about that. Integer zero is logically false and any other integer is logically true.
That said I don't add unneeded parentheses for simple expressions or sub-expressions consisting only of exponents, multiplications, divisions, additions and/or subtractions.
The example was meant to show the imperative approach which is not necessarily the best approach in Python either. They said so much in the paragraph before the example. Yes, Python was used and is used often to show imperative examples. Python is used in these examples because it is also dynamically typed, and understood by a lot of programmers. The reason it is not given in Clojure is that it is pretty hard to make imperative examples in Clojure.
Also, coming from Ruby, the way the "process" function was posted is unfortunately the way many Ruby programmers would do it despite access to filter, map and blocks.
(def s (range 0 10))
(reduce + (map inc (filter even? s)))
can be turned into Python's from functools import reduce
from operator import add
def inc(x): return x + 1
def even(x): return x % 2 == 0
s = range(0, 10)
reduce(add, map(inc, filter(even, s)))
Everything's lazy in there. It's just... not very Pythonic.I suggest Go for this use case. It's easy enough to read for this sort of thing even if you don't know the language, easy to link to running examples on the playground, and nobody will accuse you of showing Go off in a worse light than is called for because the Go equivalent of the imperative example shown really is the Go way to do it. Go is just about the most aggressively non-FP language out there today, making even Python look friendly to FP programming, despite the fact it has closures.
[P.S: I'm a Python core developer and long-time user; calling Python out is really, really not my thing!]
One cannot easily and safely do functional programming in Python due it's imperative semantics. For example variables are captured as mutable references and even a list comprehension will mutate its bindings.
If you want to do serious functional programming, use Clojure, Haskell and/or OCaml.
Edit: to the downvoters, it's about using the right tool for the job.
Anyways, if you do it with list comprehensions in Clojure it would look like this:
(apply + (for [x (range 10) :when (even? x)] (inc x)))
Well, in my opinion Python really does not let you write elegant and stateless programs that behave in an FP manner.
The sample you've chosen in particular is all fine, except that `lambda` expressions are almost never used, due to Python not being expression oriented, so there isn't much you can express with a `lambda`.
You might find your `for x in seq if even(x)` expression elegant, but it is preceded by a `return`. Python is a statement / side effects oriented language and even in your one liner, it shows.
Oh, and given that a Python "generator" is like Java's Iterable / Iterator, that's not FP either, although you can argue that the mutation can be localized (e.g. in your `sum` example), but you have to be careful about it.
And if you'll take a look at the whole Python ecosystem, there's not much FP in it to be honest. I mean, seriously, out of all the mainstream languages, I can't think of a worse language to do FP than Python, except for C maybe,
This is the best transducer explanation and breakdown I’ve seen!
Very well structured and IMO easy to follow for anyone who understands Clojure code already. Great explanation and progression that builds towards the full transducers picture! (edit: typos)
I really like this one because of the “first principles” approach of starting with reduce and building from there.
user=> (require '[clojure.core.reducers :as r])
user=> (def s (range 9999999))
user=> (time (r/fold + (r/map inc (r/filter even? s))))
"Elapsed time: 450.318641 msecs"
25000000000000
user=> (time (r/fold + (r/map inc (r/filter even? (vec s)))))
"Elapsed time: 463.632907 msecs"
25000000000000
Note the `(vec s)` instead of just `s` in the second timed run above user=> (def sv (vec s))
user=> (time (r/fold + (r/map inc (r/filter even? sv))))
"Elapsed time: 156.731683 msecs"
25000000000000
Far from a ~3x speedup (in these contrived examples), realizing the sequence in-line yields approximately the same performance as if it was operated on lazily.Something to keep in mind if you're trying to optimize your Clojure. This is still the best resource I've read on reducers/transducers :)
[edit: code formatting]
At the top of the article, we see `(reduce + (map inc (filter even? s)))` -- here, `reduce` is a two-place predicate, taking a function and a collection.
Later, `reduce` is taking a function, a 'base case' [], and a collection -- it's a three-place predicate, more in line with a signature for `fold` than what I'm used to seeing for `reduce`.
Is this a clojure specific overload thing? I'm not at all an expert in FP or anything so it might be fairly standard.
For instance, you could use a distinct sigil for an omitted argument for partial application.
TL;DR: Clojure supports multi-arity functions (sort of like operator overloading in C++, but overloading for the number of operands, not their types)
When no default value is given, the result of reduce on the first two elements is used.