A Small, Nice Thing
codahale.com
codahale.com
> [sum(t) for t in zip(a, b)]
It's clear that this alternate version is easier to understand:
> [x+y for (x,y) in zip(a,b)]
Beyond that, of course functional languages like Clojure lend themselves to creating "small, nice things." That's why functional languages like Racket/Scheme are often used to teach introductory CS courses: they're not too hard to read, and lend themselves nicely to explaining basic concepts, while not burdening the user with enormously complicated syntax. However, this can change depending on the context. I think it would be fair of the author to bring up scaling difficulties of Clojure: conventions for "small, nice things" occasionally produce really ugly abstractions at scale.
Strange, too, that infix notation (familiar to anyone who's taken arithmetic) needs explanation in Scala and Ruby, but prefix notation gets a pass in Clojure.
`map` using a lambda may be undesirable compared to a listcomp, but if you already have a function doing exactly what you want it's perfectly fine. Though in this case a better (existing) function is `operator.add`.
map(lambda x: operator.add(*x), zip(x, y))
map might not be `pythonic` but I typically pull out map/reduce/filter for the trivial cases. filter(bool, list_) is just as understandable as [i for i in list_ if i]
>>> import operator
>>> a, b = [1, 2, 3], [4, 5, 6]
>>> map(operator.add, a, b)
[5, 7, 9]
List comprehensions are a relatively late addition to the language and don't really warrant being called "more Pythonic" in this case IMO.(Yet more traditional Python would be the plain for loop, of course)
1 2 3 + 4 5 6
5 7 9 +/ (1 2 3) (4 5 6) (7 8 9)
12 15 18
as well as octave and julia's: [1 2 3] + [4 5 6]What's certain is that it's disingenuous to say that you have to explain those concepts in other languages, but not in Clojure. You might even say that you can guess what `zipped` and `sum` do, but the Clojure example really gives you no information about what's happening behind the scenes.
Learning "about" the variadicity of what are, in other languages, binary infix operations, is certainly a thing to learn, but it's a different kind of thing to learn than learning about "zipped" or "map". It's more of a "changing the way you think" kind of thing than a "knowing what tool to use" kind of thing. Like learning about destructuring pattern-matching, or actor-modelled crash-only software.
"I would say that map being variadic on the set-of-things-being-mapped, and + being variadic on the set-of-things-to-add, is more of a "semantic axiom of the language" than plain-old DSL-esque "magic."
When you debate about any LISP based language is usually my clue.
I respectfully disagree. Map is almost a universal function and IMO, anyone who knows about map will instantly grok what the Clojure version does. The reason this looks so simple in Clojure is due to the fact that the map function is variadic (as our most other functions in Clj) but that is clearly not magic for any experienced programmer.
According to Wikipedia `map` is also variadic in in D, in J, in Mathematica, in Prolog (and logtalk), in Python and in R (to an extent, `lapply` is not variadic but `mapply` can take 2+ sequences).
Variadic map is by no means limited to lisps.
Either way, I seem to remember that Clojure does have a REPL function to show you the source code for any function. You can use that to know what's going on behind the scenes.
@a «+» @b;
And, for the three (or more arrays, you just keep going) case:
@a «+» @b «+» @c;
These are called "hyper-operators": https://perl6advent.wordpress.com/2009/12/06/day-6-going-int...
>>> import numpy
>>> numpy.array([1,2,3]) + numpy.array([4,5,6])
array([5, 7, 9])
Python sequences use "+" for concatenation, but numpy arrays use them for addition. (This is why language designers should not use "+" for concatenation. The mixed-mode semantics get very strange.). List.map2 ( + ) a b;;
However, for 3 or more lists, Clojure definitely wins (since there's no variadic equivalent of map in OCaml) - let x = List.map2 ( + ) a b in List.map2 ( + ) x c;;
Which got me wondering, is there a particular reason why only a `map2` exists in OCaml? zipWith (+) a b
... and things get a little bit hairy with three lists: zipWith3 (\x y z -> x + y + z) a b c
... which is still nice but for four lists we get to the same point as OCaml: let a' = zipWith (+) a b in zipWith3 (\x y z -> x + y + z) a' c d
I guess in both OCaml and Haskell the user is expected to define zipWith4, zipWith5, etc. (or map in OCaml's case) if he fells the need to use them.There is probably a simple and elegant way to get variadic-like behavior in Haskell by folding list of list, or playing with Foldable and Sum, but it's a little bit to early in the morning for me to generalize it.
liftA2 (+)
or (+) <$> a <*> b
if you have the right type-class instance. a.Zip(b, (x,y) => x+y)
Only requires explaining method invocation, Zip(), and lambdas.