I decided to take up something completely new: Functional programming in Clojure. It’s far from being the most popular tech on the market, but on the other hand it’s a chance to experience something completely new that might come in handy in future.
One of the first striking observations is how concise it is compared to the old, heavy and static languages. Here’s an excersise: Write a function that flattens a list of objects.
Here’s what it looks like in Java (24 lines, 543 characters):
import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Hello { private static List<Object> myFlatten(Object in) { if (in instanceof List<?>) { List<Object> out = new ArrayList<Object>(); for (Object obj : (List<?>) in) { out.addAll(myFlatten(obj)); } return out; } else { return Arrays.asList(in); } } public static void main(String[] args) { System.out.println(myFlatten(asList(1, asList(2, 3, asList(3, asList(asList(3))))))); } }
Now compare that to Clojure (6 lines, 141 characters):
(defn my-flatten [v] (if(sequential? v) (vec (apply concat (map my-flatten v))) [v])) (println (my-flatten [1 [2 3 [3 [[3]]]]]))
In this case Java is awfully verbose. Some of it is related to static typing. That’s fine, we know how useful it becomes as the amount of code grows.
However, even in this trivial example the functional approach and Clojure show their strengths. All the interesting logic resides in this single line:
(apply concat (map my-flatten v))
That has little to do with static typing or richness of Clojure’s standard library. It owes to the functional approach and closures
How simple and readable is that?
FYI — your Java could be more concise. Also note, you are creating a new ArrayList for every recursion — that is certainly wasteful and unnecessary:
public static List myFlatten(Object in, List out) {
List listOut = (out == null) ? new ArrayList() : out;
if (in instanceof List)
for (Object obj : (List)in) myFlatten(obj, listOut);
else
listOut.addAll(Arrays.asList(in));
return listOut;
}
Also note, even here, most of the white space is optional.
As for your closure example, I pity the poor soul who has to read the code and figure out what is its purpose. In my experience, this would be clear to only a select few. Conciseness for conciseness sake just makes everyone’s life more difficult. To be maintainable, code should be written for clarity, not conciseness.
“I pity the poor soul who has to read the code and figure out what is its purpose”
Ditto on your Java!
The Clojure code could actually be simpler:
(defn my-flatten [x] (if (sequential? x) (mapcat my-flatten x) [x]))
Let me read the Clojure example how I comprehend it in English:
“The function my-flatten taks a value x; if x is sequential, then return the concatenated result of mapping my-flatten over x, else return a vector containing x.”
Now let me read the Java version:
Oops! I don’t know Java! What is this unmaintainable mess? I pity the soul who has to fix it.
Just kidding, I can read Java:
“The static method myFlatten takes an Object ‘in’ and an output List ‘out’ (why do consumers need to be bothered with this implementation detail?) and returns a List; if ‘out’ is null, then ‘listOut’ is a new ArrayList, else it is the value of ‘out’; if ‘in’ is a List, then for each object ‘obj’ in ‘in’ call myFlatten with ‘obj’ and ‘listOut’ (purely for side effects), else add a list consisting solely of ‘in’ to ‘listOut’; return the modified-in-place ‘listOut’.”
Does that really seem clearer?
Also you’ve got a method that takes a mysterious output parameter for some unknown reason, and which serves the dual purpose of being used for its return value (by users) and for side effects (internally). Consumers are exposed to implementation details. You can’t change your implementation without breaking the consumer’s interface. Software engineering fail.