From Michael Feathers:
OO makes code understandable by encapsulating moving parts.
FP makes code understandable by minimizing moving parts.
This explains some of the tension between object oriented programming and functional programming. The former tries to control state behind object interfaces. The latter tries to minimize state by using pure functions as much as possible.
It’s understandable that programmers accustomed to object oriented programming would like to add functional programming on top of OO, but I believe you have to make more of an exclusive commitment to functional programming to get the most benefit. For example, pure functions are easier to debug and to execute in parallel due to their lack of side effects. But if your code is only semi-functional, you can’t have the same confidence in testing your code or in spreading it across processors.
James Hague argues that 100% functional purity is impractical and that one should aim for 85% purity. But the 15% impurity needs to be partitioned, not randomly scattered across your code base. A simple strategy for doing this is to use functional in the small and OO in the large. Clojure also has some very interesting ideas for isolating the stateful parts of a program.
Related post: Pure functions have side effects
The tension may disappear with a reinterpretation of those OO concepts that make sense in a functional (or, for that matter, logical) context. Interpreting OO as being about controlling state fits an imperative context but there are far more interesting ideas in OO to bring to declarative languages (e.g. code encapsulation and code reuse, as you refer in your other post).
Perhaps that’s why multi-paradigm languages, like C++ or D, are the answer — STL, lambda expressions and function objects are nice examples of mixing the styles that works in practice.
What you’re missing in your analysis is that OO can be implemented in an FP language. Because you have things like first class functions and data structures, you can implement objects with inheritance using method tables. The only differences is that the “this” or “self” object is explicitly passed to the methods. There are plenty of papers out there that show how to implement such a thing.
In my opinion, inheritance is overrated. In most projects, I wouldn’t miss it if I didn’t have it, especially if I had interfaces (abstract base classes).
While C# is getting more FP bits all the time, the need to keep the language palatable for the “unwashed masses” forces it to make the “popular” choice as opposed to F# for example. I am also becoming increasingly partial to Nemerle (http://nemerle.org) – which is a very powerful language.
Erlang gets this right! You can isolate dirty code in processes, and because of copy semantics, it can’t undermine any pure process.
James Hague argues that 100% functional purity is impractical and that one should aim for 85% purity. But the 15% impurity needs to be partitioned, not randomly scattered across your code base.
Separating the pure from the imperative part is exactly what Monads do. The IO monad in Haskell provides the imperative outer shell, whereas pure functions typically form the core of a Haskell program. Other monads like ST provide bubbles which use restricted imperative constructs on the inside, while providing a pure interface to the outside.
My experience so far has been:
1) Inheritance is over-rated, composition is far more useful.
2) State is unavoidable, but good programming is about getting away with as little state as possible.
3) Thinking of a problem assuming you are going to write the program in a purely functional language usually results in a better design (even if you come back and write classes and maintain state in them)
In my experience, even though I work mostly in OOP stateful languages I try to “think functionally” whenever possible. Even if the compiler can’t do anything with it, I as the developer can. It becomes the wedge that forces Single Responsibility Principle, guides my refactoring, etc. The resulting code is usually easier for me to follow and easier for me to test, even if it’s not “twue functional”. And as a result, it tends to have fewer bugs in the first place. :-)
I’ll generally split my code base into “objects that are things”, which are unabashedly stateful, and “objects that do things”, which are stateless aside from other doing-things dependent objects and occasionally caching (if I can’t push that to a memoizing wrapper). It tends to work out well in practice.
OO reduces the amount of data that has to be moved around.