Object oriented programming makes code understandable by encapsulating moving parts. Functional programming makes code understandable by minimizing moving parts.
This week’s quote comes with an article, to be specific, this piece by John Carmack on functional programming. There is plenty to like in the article but two points in particular struck me:
- A function doesn’t have to be 100% pure to get many of the benefits of purity. Of course you don’t want to use the fact that it’s not 100% pure as an excuse for making it even less pure either.
- A more functional programming style often results in more parameters to a function.
A functional style does lead to more parameters, but as Carmack notes, it is much easier to test the functions in isolation. Also, if a function needs a certain set of data to do its job, it has to obtain that data from somewhere. If the data doesn’t come from the arguments passed in, it either comes from globals, or member variables of a class, and that leads us right into the issues Carmack describes with state – storing it and updating it. There are definitely costs associated with all the options, but I am beginning to think that a couple more parameters on a function might not be the worst thing in the world.
10 thoughts on “Quote of the week – object oriented vs. functional programming”
In general I agree that a lot of complexity can be avoided by writing “pure” functions. (also in OO programming).
However thinking that “pure” functions avoid state management IMO is wrong. If you HAVE state (for example mutable globals), even if you always use copy on write, at one point you NEED to update your globals again.
The big problem of OO is that it makes it easy to mix mutable and constant state (which is bad). Good OO design means looking for a way to avoid such situations (creating immutable objects, guard access to mutable objects, define “rules” how to change state in a controlled way, minimize “side effects”, separate mutable and immutable objects, etc).
To clarify: when I explicitly change state, I don’t call this a “side effect”. IMO this is the “result” of the operation (When I write ‘b = a + 3’ then I don’t say “This operation has the side effect that b will be modified”).
About “a couple more parameters don’t hurt”: If you have a couple of constant parameters and the result is a single value –> I agree.
If the method will modify multiple parameters (e.g. reference or out parameters) then IMO the function is intended to modify state. Using a function with out parameters doesn’t change that fact (except that the caller needs to explicitly update the state)
Pure functions do avoid “state management” completely. If you “have state”, such as mutable globals, no pure function will ever change those globals. If your whole program is pure (which is unlikely in C++), then the “mutable” globals will never mutate and will just be “globals”. If you’re using an immutable global to model something that has multiple states, you would represent it as a sequence of these states.
As an illustration, if I go on vacation I move around a lot to go see the different sites. My location could be seen as part of my mutable state, since it keeps changing. But if I look back at my location in the photos from the holiday, and the timestamp of each photo, they don’t change – they are immutable. I can either model my location as a mutating state, constantly overwriting my old location, or I could model it as an unfolding sequence of timestamp-location pairs. The sequence never gets longer, but until a moment has passed, that sequence instant won’t be visible yet. Likewise I could either model my movement as something that changes/mutates my location, like
void move(location& myMutableLocation), or I can model it as something which produces a new location based on an old one, like
location move(const location prevLocation). C++ isn’t particularly good at representing things like this, so I’m not advocating that we abandon mutable variables completely.
Regarding your definition of “side effect”, I find it quite an interesting perspective. But your definition of side effect is not the same as the one that’s used in functional programming. To quote Wikipedia:
Using this definition, the expression
a + 3is probably side-effect free (assuming
aisn’t something strange), and the assignment to
technically has a side effect, but practically speaking also may just be the C++ way of representing the of defining
bto have some value (depending on your style of coding). This may be a slightly counter-intuitive way of defining “side effect”, and differs from what we know it to mean in English, but we need a phrase to describe this concept and “side effect” is the standard phrase to use. Whether or not the side effect is the primary purpose of a function or operator doesn’t change its mathematical definition as a side effect.
I agree with you that adding mutable parameters to a function only addresses half the problem. It makes the dependencies more explicit, which is a good thing, but it doesn’t make it a pure function. I think it’s still better than implicitly accessing global state, and to get the benefits of functional programming half a solution is better than none, and may be all you can reasonalbly do in a particular situation.
I know the “official” definition of “side effect”. I just wanted to clarify that when I talk about “minimize side effects” I use the term somewhat differently. And honestly I still think it crazy to say that “printf” has a side effect (kind of “oh this actually does something? I just use it to burn processing time…”).
The actual point of the official definition only gets clear in context: When you encapsulate printf within a pure function (for example to log a result) then the function suddenly has a side effect…
In my definition it’s more like this: If you encapsulate a function with an “effect” in a pure function, than this function either gets an “effect” itself if the “effect” is a logical conclusion of the function (like “LogMessageToScreen()”) or a “side effect” if the “effect” isn’t that logical. (Like “Sqrt()” that internally calls “printf” to log its result).
However as I said: I know and understand the official definition.
The point about a whole pure program is as I said: Except for a simple batch processing job THERE ARE NO completely pure applications. Most regular applications explicitly are about state handling (and that’s not a property of C++ Applictions. It’s a property of applications in general). Copy-on-write will NOT avoid the state management. It will only push it somewhere else (at the place where you exchange the original data with the new one)
@Daniel If you look at game programs of the ‘How to Design Worlds’ type you will see that the big-bang function is designed so that the entire program is free of side-effects, except perphas for big-bang itself. http://www.ccs.neu.edu/home/matthias/HtDP2e/part_one.html#%28part._.D.K._sec~3adesign-world%29
That’s not completely correct.
There still is the “WorldState” variable that is updated by three different events. This is still global state that needs to be managed… (e.g. if all three events fire at the same time, you somehow need to synchronize the events to avoid that they overwrite each others data).
But basically: Yes it is possible to write state-less applications. However I still haven’t seen a regular application that is NOT a batch processing job that doesn’t need global state.
Hi Daniel. I apologize for misunderstanding your use of the word “side effect” and telling you something you already knew.
I’m not sure what you are saying about copy-on-write. You seem to be saying that its a technique which someone may incorrectly think will avoid state in a program. In what situation would it be used as such? In my mind, copy-on-write is only a useful technique to optimize impure functions. Pure functions don’t do any writing at all, and there are no such things as “copies” (since immutable copies would be always indistinguishable from each other and therefore the same object). As a caveat, in C++ a copy is never identical, since at least the address of the object must be different, even if everything else is the same. If you don’t ever observe the address of an object, and never mutate an object, then a function like
MyClass& copy(MyClass&)will seem exactly the same whether it makes a perfect copy or simply returns the original reference. (Assumptions: the copy constructor of
MyClassmakes a perfect copy, indistinguishable from the original, and has no side effects like
printf("Hey hey hey! He copied me! I can tell! I can tell!"))
Saying that except for simple batch processing jobs there are no completely pure applications is a strong statement. Either you’re saying that although it is possible to write completely pure applications, nobody ever done it, or you’re saying that it’s not possible at all. I’m assuming the latter, since no one can know so definitively about all programs ever written. Can you write a short sample program you can think of that that would substantiate your point, which is not possible to write in a pure-functional language? Then I will try to represent the same program in a pure-functional style or language.
In my previous comment I had tried to come up with an example of something that might be considered impossible without mutation (the example of modeling a person moving around), and illustrated how it could be represented in a pure functional style, but perhaps it wasn’t a convincing example.
Writing a sample application would be a bit too complex (the point is, that simple “sample” applications usually are abstract enough that they don’t really contain any (useful) functionality anymore.
But I can give you a few examples of “regular” simple applications:
– Any Editor where the user selects an object and then can edit it.
– Most Event-Driven applications (I actually suspect: all of them): They have a global event loop. Work will be done depending on the received events. After the work has been done, the application fall back into the event loop. Usually the work includes “changes” of the state (selecting something, editing something).
– Most drivers (again, I actually suspect: all of them): they internally manage “global” state of the device…
Note that if we use the “official” version of “side effect” then it’s not possible to write a pure functional application if it does any useful work (since writing to file or screen is implicitly treated as side effect).
Basically I’d say: let’s reverse the situation. Give me an example of a single application that doesn’t need internal state which isn’t a batch processing job
An example of a “batch processing job” is an image converter: input multiple images, output multiple images in different format. Such applications can be (mostly) “pure functional” if we use my definition of side effects…
If I understand your words precisely, then here is an example of an application that doesn’t need internal state and isn’t a batch processing job:
I define this application to be one that returns 0 when executed. It is a console application which has no batch processing. I’m sure this isn’t a useful answer to you. I only use it to illustrate that the problem statement isn’t very precise (or is very precise but means precisely something different to what was intended), and so it’s difficult to know where the boundary is between applications that would meet your criteria and those that don’t.
Let me take a different approach. You’ve hinted at an additional criterion of having an event loop. For example, a calculator takes input events, such as clicking the buttons, and produces output events (updates to the screen). This can be represented roughly as a function:
input events -> screen updates
In C++ this wouldn’t be a terribly useful way of looking at it, but I’ll use it for illustration. Consider the following C++ implementation of an application like this:
ScreenUpdates main(InputEvents events)
`InputEvents` is a *container* listing all the input events across the whole execution of the program. For example, if I used the calculator to add 5+3, the events might look like this:
[ program opened, '5' pressed, '+' pressed, '3' pressed, '=' pressed, program closed ]
The output type `ScreenUpdates` is also a container, which might have the following values after the main function has executed:
[ clear display, show '5', show '+', show '3', show '8', 'end program' ]
In C++ this would be a terrible implementation (and perhaps other languages), since it assumes that all the InputEvents are present before the program even starts, and the output would only be visible right before the program ends (and of course C++ isn’t designed for this model so its `main` function couldn’t have this signature), but that doesn’t mean it can’t be done like this in *any* language – one which supports sequences that don’t have to be available all at once, and functions which can start returning items of the result sequence before control reaches its end, something like a C# generator.
The point is that `main` can be a pure function which takes all input, and returns all output. There is no mutation, and no state.
This is not the way actual pure functional programs work, but is rather a simplified example that is intended to prove that it is *possible*, without going into the more complicated details. Since neither of us are aware of every single program ever written, I only need to prove that it’s *possible* to provide strong evidence against your comment that “THERE ARE NO completely pure applications”. Given a few weeks I could probably put together a language that has these exact qualities and write a calculator app in it, which would unequivocally disprove it, but I won’t do that just yet. Rather, if you’re still not convinced, I urge you to learn Haskell, and then learn GUI programming in Haskell. Haskell is pure functional, and has no such thing as mutation or side effects, but yet people write real programs in it. It does its IO by a functional pattern called monads.
I’m enjoying your quotes series, thanks. I too am getting a lot of mileage out of a functional *style*, if not pure functions, *period*.