One day, you're browsing through your code, and you notice two big blocks that look almost exactly the same. In fact, they're exactly the same, except that one block refers to "Spaghetti" and one block refers to "Chocolate Moose."
System.out.println("I'd like some Spaghetti!");
System.out.println("I'd like some Chocolate Moose!");
System.out.println("I'd like some Chocolate Moose!");
These examples happen to be in Java, but even if you don't know Java, you should be able to follow along.
The repeated code looks wrong, of course, so you create a method:
static void swedishChef(String food) {
println("I'd like some " + food + "!");
}
static void println(Object obj) {
System.out.println(obj);
}
swedishChef("Spaghetti!");
swedishChef("Chocolate Moose!");
println("I'd like some " + food + "!");
}
static void println(Object obj) {
System.out.println(obj);
}
swedishChef("Spaghetti!");
swedishChef("Chocolate Moose!");
OK, it's a trivial example, but you can imagine a more substantial example. This is better code for many reasons, all of which you've heard a million times. Maintainability, Readability, Abstraction = Good!
Now you notice two other blocks of code which look almost the same, except that one of them keeps calling this method called boomBoom and the other one keeps calling this method called putInPot. Other than that, the code is pretty much the same.
println("get the lobster");
putInPot("lobster");
putInPot("water");
println("get the chicken");
boomBoom("chicken");
boomBoom("coconut");
putInPot("lobster");
putInPot("water");
println("get the chicken");
boomBoom("chicken");
boomBoom("coconut");
Now you need a way to replace the block in a method with another block. This is an important thinking, because it increases the chances that you'll be able to find common code that can be stashed away in a method.
interface Block<P> {
void apply(P p);
}
static void cook(String food1, String food2, Block cooker) {
println("get the " + food1);
cooker.apply(food1);
cooker.apply(food2);
}
cook("lobster", "water", new Block<String>() {
public void apply(String food) { putInPot(food); }
});
cook("chicken", "coconut", new Block<String>() {
public void apply(String food) { boomBoom(food); }
});
void apply(P p);
}
static void cook(String food1, String food2, Block cooker) {
println("get the " + food1);
cooker.apply(food1);
cooker.apply(food2);
}
cook("lobster", "water", new Block<String>() {
public void apply(String food) { putInPot(food); }
});
cook("chicken", "coconut", new Block<String>() {
public void apply(String food) { boomBoom(food); }
});
Look! We're replacing the block.
Can your brain think this?
Wait... suppose you haven't already defined the methods putInPot or boomBoom. Wouldn't it be nice if you could just write them in the apply method? Besides, calling the cook method seems verbose. Using proper variables will make them clearer than writing them inline.
Block<String> putInPot = new Block<String>() {
public void apply(String food) { println("pot " + food); }
};
Block<String> boomBoom = new Block<String>() {
public void apply(String food) { println("boom " + food);}
};
cook("lobster", "water", putInPot);
cook("chicken", "coconut", boomBoom);
public void apply(String food) { println("pot " + food); }
};
Block<String> boomBoom = new Block<String>() {
public void apply(String food) { println("boom " + food);}
};
cook("lobster", "water", putInPot);
cook("chicken", "coconut", boomBoom);
Jeez, calling the cook method looks clearer. When creating an instance of an anonymous class on the fly, you can give it a proper name and then toss it into a method.
As soon as you start thinking in terms of anonymous class instances as arguments, you might notice code all over the place that, say, does something to every element of a list.
List<Integer> numbers = asList(1, 2, 3);
List<Integer> multipliedWith2 = new ArrayList<>();
for (Integer number : numbers) {
multipliedWith2.add(number * 2);
}
List<Integer> multipliedWith2 = new ArrayList<>();
for (Integer number : numbers) {
multipliedWith2.add(number * 2);
}
Doing something to every element of a list is pretty common, and you can write a method that does it for you:
interface Mapper<P, R> {
R apply(P p);
}
static <T, R> List<R> map(List<T> lt, Mapper<T, R> mapper) {
List<R> mapped = new ArrayList<>();
for (T elem : lt) {
mapped.add(mapper.apply(elem));
}
return mapped;
}
R apply(P p);
}
static <T, R> List<R> map(List<T> lt, Mapper<T, R> mapper) {
List<R> mapped = new ArrayList<>();
for (T elem : lt) {
mapped.add(mapper.apply(elem));
}
return mapped;
}
Now you can rewrite the code above as:
Mapper<Integer, Integer> multiply2 = new Mapper<Integer, Integer>() {
public Integer apply(Integer number) { return number * 2; }
};
List<Integer> multipliedBy2 = map(numbers, multiply2);
public Integer apply(Integer number) { return number * 2; }
};
List<Integer> multipliedBy2 = map(numbers, multiply2);
Another common thing with lists is to combine all the values of the list in some way.
static Integer sum(List<Integer> numbers) {
Integer sum = 0;
for (Integer number : numbers) { sum += number; }
return sum;
}
static String join(List<String> strs) {
String joined = "";
for (String str : strs) { joined += str; }
return joined;
}
println(sum(asList(1, 2, 3)));
println(join(asList("a","b","c")));
Integer sum = 0;
for (Integer number : numbers) { sum += number; }
return sum;
}
static String join(List<String> strs) {
String joined = "";
for (String str : strs) { joined += str; }
return joined;
}
println(sum(asList(1, 2, 3)));
println(join(asList("a","b","c")));
sum and join look so similar, you might want to abstract out their essence into a generic method that combines elements of a list into a single value:
interface Reducer<R, P> {
R apply(R r, P p);
}
static <T, R> R reduce(List<T> lt, Reducer<R, T> reducer, R init) {
R r = init;
for(T elem : lt) { r = reducer.apply(r, elem); }
return r;
}
static Integer sum(List<Integer> numbers) {
Reducer<Integer, Integer> sumUp = new Reducer<Integer, Integer>() {
public Integer apply(Integer sum, Integer number) { return sum + number; }
};
return reduce(numbers, sumUp, 0);
}
static String join(List<String> strs) {
Reducer<String, String> joinAll = new Reducer<String, String>() {
public String apply(String all, String str) { return all + str; }
};
return reduce(strs, joinAll, "");
}
R apply(R r, P p);
}
static <T, R> R reduce(List<T> lt, Reducer<R, T> reducer, R init) {
R r = init;
for(T elem : lt) { r = reducer.apply(r, elem); }
return r;
}
static Integer sum(List<Integer> numbers) {
Reducer<Integer, Integer> sumUp = new Reducer<Integer, Integer>() {
public Integer apply(Integer sum, Integer number) { return sum + number; }
};
return reduce(numbers, sumUp, 0);
}
static String join(List<String> strs) {
Reducer<String, String> joinAll = new Reducer<String, String>() {
public String apply(String all, String str) { return all + str; }
};
return reduce(strs, joinAll, "");
}
Using a language such as JavaScript will have an easier way to do this kind of stuff. Many older languages simply had no way to do this kind of stuff. Other languages let you do it, but it's hard (for example, C has function pointers, but you have to declare and define the function somewhere else). Object-oriented programming languages aren't completely convinced that you should be allowed to do anything with functions, such as Java.
If you want to simulate first-class functions, Java required you to create a whole object with a single method called a functor if you wanted to treat a function like a first class object. Combine that with the fact that many OO languages want you to create a whole file for each class, and it gets really klunky fast. If your programming language requires you to use functors, you're not getting all the benefits of a modern programming environment. See if you can get some of your money back.
When using a language with first-class functions, will you automatically own the ability to write code such as above? Definitely, it's verbose without first-functions, but the point is the process of thinking. Hum? How much benefit do you really get out of writing itty bitty functions that do nothing more than iterate through a list doing something to each element?
Well, let's go back to that map function. When you need to do something to every element in a list in turn, the truth is, it probably doesn't matter what order you do them in. You can run through the array forward or backwards and get the same result, right? In fact, if you have two CPUs handy, maybe you could write some code to have each CPU do half of the elements, and suddenly map is twice as fast.
Or maybe, just hypothetically, you have hundreds of thousands of servers in several data centers around the world, and you have a really big list, containing, let's say, again, just hypothetically, the entire contents of the internet. Now you can run map on thousands of computers, each of which will attack a tiny part of the problem.
So now, for example, writing some really fast code to search the entire contents of the internet is as simple as calling the map function with a basic string searcher as an argument.
The really interesting thing I want you to notice, here, is that as soon as you think of map and reduce as functions that everybody can use, and they use them, you only have to get one supergenius to write the hard code to run map and reduce on a global massively parallel array of computers, and all the old code that used to work fine when you just ran a loop still works only it's a zillion times faster which means it can be used to tackle huge problems in an instant.
Lemme repeat that. By abstracting away the very concept of looping, you can implement looping any way you want, including implementing it in a way that scales nicely with extra hardware.
And now you understand something I want to ask: are you a programmer writing nothing without first-class functions?
Without understanding functional programming, you can't invent MapReduce, the algorithm that makes Google so massively scalable. The terms Map and Reduce come from Lisp and functional programming. MapReduce is, in retrospect, obvious to anyone who remembers from their functional programming that purely functional programs have no side effects and are thus trivially parallelizable.
Ok. I hope you're convinced, by now, that programming languages with first-class functions definitely let you find more opportunities for abstraction, which means your code is smaller, tighter, more reusable, and more scalable. Without first-class functions, however, you can use the same process of thinking to write reusable and extensible programs. Lots of Google applications use MapReduce and they all benefit whenever someone optimizes it or fixes bugs.
I'm confused every time. The most productive programming environments definitely let you easily work at different levels of abstraction. Crappy old FORTRAN really didn't even let you write functions. C had function pointers, but they were ugleeeeee and not anonymous and had to be implemented somewhere else than where you were using them. Java made you use functors, which is even uglier. As Steve Yegge points out, Java is the Kingdom of Nouns.
So what? Cannot a C program be written in object-oriented ways? With powerful first-class functions, will your brain level up automatically? Lambda will be introduced into JDK8. If you even write code such as above, you'll benefit from JDK8 Lambda directly.
static Integer sum(List<Integer> numbers) {
return reduce(numbers, (sum, number) -> sum + number, 0);
}
static String join(List<String> strs) {
return reduce(strs, (all, str) -> all + str, "");
}
return reduce(numbers, (sum, number) -> sum + number, 0);
}
static String join(List<String> strs) {
return reduce(strs, (all, str) -> all + str, "");
}
If your brain cannot think this, it's mostly likely to just take JDK8 Lambda as syntax sugars. There's no way your brain will be leveled up automatically because you use a language with first-class functions. Don't forget those not object-oriented codes written with Java.
PS. Most sections are copied from Can Your Programming Language Do This? . Doing it on purpose is simply for earning your smile.