The Java blogosphere is currently discovering or introducing to the uninitiated the benefits of closures. To me, posts like Reginald’s seem to present closures as an advance from OO. This is kind of strange, given that the historical order is exactly the other way round!
When I started digging into OO languages in the 90s, I had already spent some time with Scheme and Tycoon (a language based on Luca Cardelli‘s Quest), all of them higher-order functional-imperative programming languages. I did some non-trivial compiler programming in both languages, and it certainly was fun, in a way similar to solving math riddles.
I learned that OO was great because it names things. OO allows a traceable connection between the conceptual design level and the implementation level. Concepts have names, so you can talk about them, between programmers and architects. The promise of OO was to replace the dazzling complexity (and intellectual challenge) of functional programming with something more manageable.
Language is not understood best when you have removed as much as possible. Would you rather read a novel, or an algebra textbook? Just imagine taking your green, yellow and red pen and marking up a book by Neal Stephenson.
Conciseness comes at a price: The more compressed your code, the harder it is to understand what the consequence of some change would be. If you have left out everything you do not need for your current goal, what if the goal moves? If (e.g.) you find that what was once a closure should now be a class, e.g. because some code wants to inspect it instead of just running it, you’ll be crying for another super-advanced refactoring IDE.
“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?” (Kernighan)
Higher-higher-order functions and inferred types, or lists of lists of pairs whose cdr happens to represent some concept (except when it represents another concept) will all give you that occasional “ouch, there’s a error message, but I don’t think there should be!” The code looks fine, so why does the type checker not like it? (it probably took something you wrote, took it all wrong, drew impressive conclusions and is now reporting a problem at a completely different place). The program state and the code both look fine, but they differ in the level of nesting of some unnamed construct (e.g. a closure or a list where a value was expected).
I strongly associate higher-order functional programming with long stretches of uninterrupted concentrated programming work, and single-person projects. In contrast, OO gives you a pragmatic, shallow paradigm for making software intelligible and available for cooperation.
Closures can be a nice tool for creating your own DSL, to make your code get closer to the design model. But closures can also take you further away from the design model, by creating constructs that only make sense in the abstract.
Object-oriented programming was introduced to fill a need not satisfied by functional and imperative languages. Remember: Going from functional to OO is an advance.
People often argue that using closures for a function such as ‘map’ is
so much more readable than just allocating a list and using a for
loop.
The thing is, after you have read the for loop version many times, it
becomes just as easy to read. You see it, you *know* what it is doing,
and you sort of tune out the boilerplate, and just see the code that
is actually doing the work.
Once that has happened, does the difference in code size between
return list.mappedWith({
String s => s.toUpperCase()
});
and
List newList = new ArrayList();
for(Iterator iter = newList.iterator(); iter.hasNext(); iter.next()) {
String element = (String) iter.next();
newList.add(element.toUpperCase());
}
return newList();
really make it worth the complexity of not being able to see inline
what the mappedWith() function does?
HOF or OOP? Yes!
http://weblog.raganwald.com/2007/02/hof-or-oop-yes.html
I don’t think Java or C++ are less complex/challenging than Scheme. To the contrary.
“Language is not understood best when you have removed as much as possible. Would you rather read a novel, or an algebra textbook?”
Actually, the notation and other measures taken to ensure brevity in math arose by no accident. They are essential. Math must consider difficult abstract concepts and the only way to effectively communicate that is to do it in a special way. Math is understood far, far better when you have symbols instead of words. The brevity there makes things that would otherwise be impossible to explain possible. This is the math that makes it possible to build suspension bridges and all the other things in our daily lives we take for granted.
“Conciseness comes at a price: The more compressed your code, the harder it is to understand what the consequence of some change would be.”
What?
“I strongly associate higher-order functional programming with long stretches of uninterrupted concentrated programming work, and single-person projects.”
Object-oriented programming is therefore for when you are in an office full of screaming morons and no one gets any work done? Does OOP magically make software easy? I doubt it.
“Object-oriented programming was introduced to fill a need not satisfied by functional and imperative languages. Remember: Going from functional to OO is an advance.”
Thinking you don’t need to take functions seriously is ridiculous and is why you have entire “design patterns” that are invisible in functional languages.
Have you heard about the Scala programming language? You’ve been claiming throughout this article that functional programming and OOP are somehow incompatible and it’s undesirable to have both; I think that’s an unreasonable bifurcation. think both are useful in different situations. Scala unifies functional programming and object-oriented programming nicely. It runs on the JVM. You should have a look. http://scala-lang.org .
@Alf: The example you cite – closures for iteration abstraction – is the other example where closures excel. The first example being the currying operations Reg cited in his post. Resource allocation/deallocation might be another good example.
All of these are mechanisms for avoiding to name things that do not need names. My line still is: OOP is an advance over functional programming. Because functional programming tempts you to name too few things, and makes you create abstractions in a way that is hard to communicate to anyone unfamiliar with your code.
The challenge in my view is in adding functional features to an OO language without falling into the nameless abstraction trap.
To Warren: I am not talking about the complexity of the language, but about the complexity of a typical program written in that language. Schemers have a tendency to outsmart themselves (at least I did when I was hacking Scheme). About math being the perfect world description, I doubt it – math is not very good at anchoring your model in a conceptual context, math’s power kicks in as soon as you leave the reallity plane and start working purely at the level of abstraction. Lots of software today is not system software, so we need to relate to “real” requirements. And does OOP make software easy? Well, a systematic approach always means not doing everything you could, so that the things you do have some guaranteed properties. Which implies restricting yourself to the things you can now do with sensible effort. And about design patterns: Have you seen the “OO” design pattern in Scheme? Ugly as hell. Scala: always wanted to take a look at that, and I definitely will, if I just wasn’t in an office full of screaming morons most of my time ๐
Sorry, it’s late, I’ll come back to the points I missed!
What about HOOOP? (Higher-Order Object-Oriented Programming)
“What about HOOOP?”
I can’t think of a post yet, maybe you’ll think of one. But the title jumps out and begs for someone, anyone to write an essay:
“HOOOP Dreams”
http://www.imdb.com/title/tt0110057/
@Alf: Upon re-reading your comment, I realize you argued for the expanded idiom, instead of a closure abstraction.
You ask “does the difference in code size really make it worth the complexity of not being able to see inline what the mappedWith() function does?”
The problem with idioms is that apart from the metaphorical meaning (the reason why you chose to use the idiom) they also have a concrete meaning, which is often much more concrete than, or completely different from, the situation in which you use the idiom. If somebody does not know the idiom and takes you literally, it’s comedy time.
Translating this to code, the problem is that the expanded form – the idiom – prescribes more than it should. Is it really necessary that a variable “iter” is introduced (and can be inspected in a debugger) that is bound to an object with a unique identity? And that the results of the iterator are delivered one after the other?
I would say – no, code size alone is not it. The real difference is between
ListUtil.map(l,fun(String s){ s.toUpperCase() })
and
l.map(fun(String s){ s.toUpperCase() })
the latter being object-oriented, with an implementation that depends on the concrete type of collection, and the former being just an abbreviation (where the implementation might be exchanged in theory, but not in practice, for backwards compatibility reasons).
There’s more to say about this – probably enough for another post!
“If somebody does not know the idiom and takes you literally, itโs comedy time.”
I’m not so worried about the one who hasn’t seen the idiom before.
I’m more worried about the person who has seen the idiom many times before, and has learned to “read” it as a unit.
Example: Did anyone actually notice the bug in the inlined code I used in my example? I wanted to prove a point: Inlining of control flow constructs out of fear that abstraction would make the code abstruse and unreadable is not only morally wrong; it can actually cause its own class of bugs.
(let me apologize for the dishonest phrasing of my first comment — I wanted to show how easy it is to misread inlined code, and I couldn’t figure out a way to be frank about it without ruining the “example”)