Why I love Common Lisp and hate Java, part II – code examples
kuomarc.wordpress.com
kuomarc.wordpress.com
I also don't understand the reason for writing the code in an intentionally non idiomatic style. If you want to convince someone why something is better; show them how it would be done properly. The way it has been presented instead insults the audiences intelligence by presupposing that they would be unable to understand this material, that the author has admittedly only recently picked up himself.
I don't think (reduce min list) is going to blow anyone's mind, and writing a function to iterate through the indexes of a list will do more to confuse than to enlighten.
not to mention; we've been here before haven't we - http://c2.com/cgi/wiki?SmugLispWeenie
I see your point of insulting the audiences intelligence, but don't you think to throw in a tail-call optimized recursive function - or (reduce min list) for that matter - would be a moot comparison and distract the reader from the point?
When you try to make a point by comparison, say the weather in Hong Kong versus Toronto, you wouldn't go gauge the weather in HK on a hot summer day at 4pm in the Central District, while in Toronto you go out in the burbs in the middle of a deserted, snowy night?
I'm not sure many Java programmers are ignorant about, say, the reflection APIs.
There's no way to teach someone how to programatically manipulate code if they can't mentally parse the code in the first place. Even if you were to completely teach Lisp as a language before introducing metaprogramming you still run into the experience issue. It takes personal experience to understand where and when to use macros. Like you said yourself in the article, most things can be expressed as a normal function. It takes the experience of writing a lot of code to be able to make the judgement of what actually belongs as a macro, and not just a function.
I understand that metaprogramming is very exciting because it opens up so many doors, but it's easy to forget about everything that you had to already know to get to the point where it becomes useful. Lisp makes it very easy to repeat this problem because the language itself is so small and consistent. It doesn't take long to get to the point where you feel comfortable enough to try writing macros.
It seems that you want to share how great metaprogramming can be and how much it simplifies the work you're doing. That's great, you and I can look at it and appreciate it. But framing it as though it's appropriate for Java programmers as an introduction to Lisp just serves to make Lisp programmers look arrogant, and builds even more resent amongst Java programmers.
So please hang in there ;) I'm getting there.
Also, there are different levels of audience. Diving straight into complex macro structures might just be ineffective.
Above all, I'm not trying to preach to the converted. You seem to be well aware of the powers of Lisp, so not my intended audience. But thanks for reading and your input anyway ;)
More seriously though, your macro is dangerous as a teaching example because it introduces variable capture (on i and ans) and unintentional repeated evaluation (of list), which are big gaping traps for the unwary.
Also, try passing #'(lambda (a b) (< a b)) as the pred argument to your macro, and see what happens.
Except that empirical evidence says it is a big deal: code size is the best measure of complexity we have, and error rates grow superlinearly with it.
It's interesting how nearly everyone in the software profession pooh-poohs that finding. What we "know" (i.e., what we're used to) is more important to us than evidence. If we took the evidence seriously, we would work seriously to find ways to make smaller systems.
Is it verbosity that is the best measure of complexity, or is it code size? Yes verbosity leads to more code, but it's not the only thing that does. Operating at the wrong level of abstraction also will, and in a way that makes maintenance extremely difficult.
Unfortunately I've not seen any evidence compares the impact of boilerplate vs duplication.
I don't know of any way to separate these. Look at it this way: to make small systems, we need both powerful languages and good programmers.
If you don't get to choose what language to write in (as is the case on most software projects), it's still better to write less code. But how can we measure the impact of language choice? I don't think we can. You'd have to write the system twice, holding the other variables constant. But writing the system once already changes everything.
I don't disagree with you about the importance of good abstractions for reducing code size. Where we might disagree a bit is on the influence of programming language on what abstractions get created. It's fashionable in the industry to downplay the importance of language (algorithms matter more, libraries matter more, programmers matter more - pretty much everything is supposed to matter more). But I think the language we're writing in influences the kind of thing we write, and thus the kind of thing we think, all the way up to how we conceptualize the problem. Maybe it isn't the strongest influence at any point, yet it's compounded over every decision made in the lifetime of the system. Once a system has passed the embryonic stage, by far the most important factor in how it develops further is how the existing parts already work. Language seems to me deeply involved in conditioning this trajectory - why the system grows this way and not another. Not every idea is equally likely in every medium. I think we see this in the fact that distinct languages give rise to such distinct programming cultures.
As an aside, this is something the typical way of comparing languages -- juxtapose a known algorithm written in X to the same thing written in Y -- fails to address.
I completely agree also that the language can have an impact on the abstractions you choose. Some abstractions are obvious in one language and obscure in another. I wouldn't defend Java against a better example than in the original post, it certainly has its weaknesses. But I find that most examples just show bad Java code, not that Java itself is bad, which is not enlightening and frankly becomes tiresome.
On a related note, I think a lot of people underrate the influence of tools on code quality too. They might complain Java IDEs are a crutch that merely let you work around the language's weaknesses. I think they're a lot more than that -- when I start working in a seriously powerful IDE for the first time I was surprised how much it influenced my coding. Powerful automatic refactoring just makes it so trivial to do the Right Thing (give something a better name, factor out a method, etc). Beforehand I would have said that all you need is discipline to create code of the same quality without the IDE. Now I'd say even discipline and good intentions have their limits.
So, Java programs must be much less buggy than CL programs, because most of the things are already available in well tested, open source libraries, used by millions, and with several version out.
So, a Java programmer gets to write just the domain logic, and not also implement lots of things from scratch because they are not available.
I'd take, say:
2K LOC Java Program + battled tested lib for X feature
over
1K LOC CL Program + 1-2 K LOC custom CL X implementation
Not "by my logic". By an absurd interpolation of my logic to it's far out extreme. It's as if someone points out the existence of gravitational attraction, and someone says "by your logic, everything should be pressed together".
The existence of a huge ecosystem of libs and APIs for major languages, and much less so, for lesser used languages, is not disputable. That people programming in languages with more available libs don't tend/need to reinvent the wheel as much as people programming in languages without, is also a basic observation.
Not to mention that libraries != software.
$ ghci
Prelude> let getFromList ls pred = foldl (\x y -> if x `pred` y then x else y) (head ls) (tail ls)
Prelude> getFromList [1..10] (<)
1
Prelude> getFromList [1..10] (>)
10;Clojure example (defn get-from-list [list pred] (reduce #(if (pred %1 %2) %1 %2) list))
(Perhaps because I said the lisp looked like vomit? but it does!)
int getMax(int... list) { int max = list[0]; for (int i : list) max = Math.max(max, i); return max; }
or if you have a collection Collections.min(list);
The Lisp looks like vomit, because I wrote it with a Java mind. And for you to say that it looks like vomit, well.. I guess that's why ;)
Your comment did not add any value to the discussion. However, it is emotionally loaded and might antagonize readers; I'd rather keep my blog clean.