2 is a code smell
tech.puredanger.com
tech.puredanger.com
This does of course not apply if you use a deliberately restricted language that has just one type of collection traversal, in that case you are better off with varargs, as n*1 < n+1.
http://www.catb.org/jargon/html/Z/Zero-One-Infinity-Rule.htm...
If the code is simpler then I don't see how YAGNI comes into play. Why would you do something that is both more complicated and less amenable to future needs?
tl;dr Clojure is awesome. And use
(defn add-vectors [& vs]
(vec (apply map + vs)))
instead of (defn add-vectors [va vb]
[(+ (first va) (first vb)) (+ (second va) (second vb))])
since in Clojure code that operates on 1 or N things is usually better than some other fixed value. (Like 2.)I do think the author has a point, but to me the logical complexity progression for a piece of code is:
1. Make it work in the "1" case.
2. If necessary, make it work in the "2" case.
3. If necessary, make it work in the "n" case (n variable).
I find this particularly useful for larger decisions: I can make this work right now if I add this tiny little wart? No problem. Another thing came up and a similar wart is required? Ok, I guess. A third? Time to find the structure that my code is telling me about and refactor to expose it.
I'd rather call fold or foldr myself and KNOW how it handled more than 2 arguments. I'd rather do it for add-vectors too, just so it was consistent. You have to define primitives somewhere. Even if what you call elsewhere is some fluffy handle-everything function. At the very least, I wouldn't call it a smell.
It really depends on the scope of the problem. Is this supposed to be a fully-functional addition function, or is this a throwaway helper function that the coder wrote because they were doing that specific operation many times, and they didn't want to type the entire thing out? If it's the first case, then it may be an issue. If it's the second, it's just poorly named, and not necessarily an issue at all.
(Of course, the objection to data having only two things doesn't hold either if it's a recursive data type -- cons lists, binary trees, etc... -- but you know what I mean.)
Knowing that difference and applying judgement is a major differentiator between inexperienced programmers and experienced ones. That, I think, is what heyrhett was getting at.
Two minutes to make a minor simplification and learn a generally good way of approaching things with the tool at hand (clojure)? Count me in.
1 1 1 + 2 2 2
3 3 3
(1 1 1; 2 2 2; 3 3 3) + (4 4 4; 5 5 5; 6 6 6)
(5 5 5
7 7 7
9 9 9) > c(1, 1, 1) + c(2, 2, 2)
[1] 3 3 3
> matrix(c(1, 1, 1, 2, 2, 2, 3, 3, 3),
+ ncol = 3, byrow = TRUE) +
+ matrix(c(4, 4, 4, 5, 5, 5, 6, 6, 6),
+ ncol = 3, byrow = TRUE)
[,1] [,2] [,3]
[1,] 5 5 5
[2,] 7 7 7
[3,] 9 9 9
Not quite so concise and elegant but it's pretty straightforward stuff for most R users. def dot(v1, v2):
return sum(i * j for i, j in zip(v1, v2))
Very functional, very pretty, very straightforward. But it was slow, both in CPython and PyPy. A bit of bytecode analysis and profiling later, we realized that unrolling would be faster by several orders of magnitude. So I checked this in: def dot2(u, v):
return u[0] * v[0] + u[1] * v[1]
def dot3(u, v):
return u[0] * v[0] + u[1] * v[1] + u[2] * v[2]
And the bottleneck went away. From the Zen of Python: "Practicality beats purity."http://docs.scipy.org/doc/numpy/reference/generated/numpy.do...
If you're writing code where dot products are not something you do a lot of, then keeping a clean, idiomatic functional implementation is probably fine.
If you're writing code where dot products happen a lot, such as on real-time three-dimensional games (which happens to by my oeuvre), then the performance increase of having specific, unrolled implementations is huge.
Just think of a vector of 3 elements as being one entity, and therefore it still obeys the 0, 1, Inf rule mentioned elsewhere in the comments.