Why Why Functional Programming Matters Matters
weblog.raganwald.com
weblog.raganwald.com
"Why \"Why Functional Programming Matters\" Matters" Considered Harmful
Why "\"Why \\\"Why Functional Programing Matters\\\" Matters\" Considered Harmful" Matters
What does "powerful" mean, in programming lanugages? I can think of three meanings:
(1) what it can do. Once a language is Turing complete (call this 1.1), in one sense it can do anything. In another sense (1.2), maybe it can't: I wouldn't attept to write a Unix kernel in Java or a 3-D graphics library in Python because in both cases they are too high level of the task.
(2) how easy it is to program in? I.e. the number of programmer-hours it takes to solve a problem in that language.
(3) how easy it is to automatically reason about programs written in the language. This includes tools such as compilers, source code coverage tools, etc.
Now let's examine the original statement by these 4 criteria. We'll also consider the obverse, i.e. whether you can make a language less powerful by adding features.:
1.1 - if a language is already Turing-complete, removing features won't make it more so. Nor will adding features make it less powerful. But Turing-completeness is a trivial sense, as the article points out.
1.2 - adding eatures can make a language less powerful in this sense, because it can reduce the things it can do. For example, a big complex language with a large standard library might not work on a simple embedded controller, so in that sense it could do less. Or a language might require a certain model of multitasking that means it can't run on certain OS's (e.g. Java couldn't run on early versions of Windows (IIRC either 3.1 or 95 -- I don't remember the details). Or consider that Java is "write-once run anywhere"; adding lots of low level features might imperil this.
2 -- adding features can certainly reduce power here too, e.g. if you have a large C++ program written by a team of programmers each of whom uses a different subset of the language.
3. -- again, adding features can reduce power. For example, in Fortran, an optimising compiler can make assumptions about the code that a C++ compiler can't make because C++ allows pointers to point to anything, e.g.:
int foo(int a, int* b){
a = 1;
*b = 2;
bar(a); /* we don't know here if bar is being called
with parameter of 1 or 2 */
}
So I think the original proposition is false in practical terms.In a game design where you have important information about a rule smeared all over the object hierarchy, you have very poor separation of concerns. It looks at first like there’s a clear factoring “Baltic Avenue has a method called isUpgradableToHotel,” but when you look more closely you realize that every object representing a property is burdened with knowing almost all of the rules of the game.
Hmm,
A "noun" or OO language would make each property part of the property class and that would solve the problem so-far as he's articulating it.
The thing is, how would a 'verb' oriented language deal with a very specific kind of noun, one which both reacting to general verbs differently and required it's own special verbs. It seems like this special noun would 'smeared' around the verbs to a worse extent. If Baltic Avenue has some weird behavior, it seems cleaner to have it as separate subclass than having it's behavior taken into by various conditionals.
Similarly, think about the design of a Chess game. The verbs would be 'move', 'capture'. It seems cleaner to make these verbs into 'messages' or member functions that each piece responds to according to it's class, rather than creating a single method that might call submethods.
Still, it's a nice discussion.
Think of each individual community chest card. Think of "can I mortgage this property?". Think of "if the reading railroad is not owned, take possession of it." There are a lot of rules in monopoly, and some of them have to do with the combination of several pieces of state: the state of the player, the state of a piece of property, whose turn it is, etc. Trying to handle certain behavior inside the Baltic Avenue subclass might be problematic because you might not have access to some necessary state. And even if you have access to it, getting that state from the baltic avenue class might be a serious violation of concerns.
A helpful oversimplification is to say OO "prefers dumb code and smart data", while FP prefers "smart code and dumb data".
In an FP monopoly, the board is a set of dumb datastructures. The rules are functions that modify the board. Each community chest card can be a single function, that takes the state of the old board, and returns the state of the new board. The function is simple, it just reaches in and mucks with the single state of the world datastructure. Now your property objects are not weighed down with how to handle community chest card #35. The code is also simpler, because no single class has functions for how to respond to each rule in monopoly.
Another extremely powerful aspect of FP is first class functions. The actions a player is allowed to take can just be a list of functions. When players change turns, you can run a function on the list of allowed actions that adds or removes functions as appropriate.
This makes a lot of sense, but it seems backwards in a way: FP is as much about the realization that code is data as it is about any specific way of doing things. Your list of functions for "actions a player is allowed to take" certainly seems like "smart data" to me.
The logic of movement or of capturing isn't a quality of the chess pieces. The ways a piece can move or capture are qualities of those pieces. Having #moveFrom:to: as a message to the game-board allows you to keep all the code related to the way capturing and movement in chess works together, and keep the code for describing the movements of an individual type of piece with that type of piece. It also means that a piece doesn't need to know where it is on the board, or even what board it is on. You could even use a single object for all knights, another for all rooks, and so on without any negative consequences.
However, if you do do that, there is a risk of introducing additional complexity in the Board class when it comes to dealing with castling, pawn double initial moves, en passant, etc. Double initial moves could be handled with a message on Pawn(#hasMoved) and using two Pawn objects, one with hasMoved set and one without it. The same could be done for King and Rooks for castling. En passant would probably require keeping an extra instance variable on Board to keep track of double initial moves.