Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

Why the debate on Object-Oriented vs Functional Programming is all about composition

In a previous post, I laid out a framework for code quality that divides the qualities of code into a few categories like fitness, correctness, clarity, performance, maintainability and beauty. However, let’s forget all that for the moment and talk about composition :-)

composition
noun 1. the act of combining parts or elements to form a whole.

In programming terms, composition is about making more complex programs out of simpler programs, without modifying the simpler pieces being composed–where the program could be anything from a single CPU instruction to an operating system with apps.

Do objects compose?

In object-oriented programming (OOP), the unit of composition seems to be an object (or class). Do objects compose? The answer to that is complicated, as there isn’t even a commonly accepted definition of object orientation. It’s mostly a “I know it when I see it” type of thing.

Objects that represent data structures with little behavior usually do compose. This is also what we can model in class diagrams: one object owns or contains another. This kind of composition doesn’t really have much to do with being object-oriented: struct or tuple types compose just as well as data objects.

For objects with complex behavior, there aren’t any well-defined compositions. Even UML diagrams give up on specifying the nature of such composition and simply allow you to say that “A depends on B”. The nature of the dependency is completely ad-hoc. B might be used internally in a method, passed as a constructor argument or method parameter or might be returned from a method.

OOP puts code and data close together, so we want to know if data and behavior taken together can compose. There’s no answer for the general case, because objects can have very complex and incompatible behavior patterns. Especially if an object is observably mutable (which seems to be the usual case with OO code), it has a potentially complex life cycle. And objects with different life cycles can easily become non-composable.

I think the real composability and reusability in object-oriented code doesn’t come from object-oriented design at all: it comes from abstraction and encapsulation. If a library manages to hide a potentially complex implementation behind a simple interface, and you only have to know the simple interface to use it, then that doesn’t come from some inherent property of object-orientation, but a basic module system that limits which symbols are exported to other modules, whether the modules are classes, namespaces, packages or something else. Such module systems are present in both OO and non-OO languages.

In summary, I think objects do not compose very well in general – only if the specific objects are designed to compose. Immutability helps to make objects composable by eliminating complex life-cycles and essentially turning the objects into values. Encapsulation also improves composability, and is put to good use in well-designed OO code, but is not unique to OO.

Do functions compose?

Functional programming (FP), on the other hand, is at its very core based on the mathematical notion of function composition. Composing functions f and g means g(f(x)) – f’s output becomes g’s input. And in pure FP, the inputs and outputs are values without life cycles.

It’s so simple to understand compared to the numerous and perhaps even indescribable ad hoc compositions possible in OOP. If you have two functions with matching input and output types, they always compose!

More complicated forms of composition can be achieved through higher-order functions: by passing functions as inputs to functions or outputting functions from functions. Functions are treated as values just like everything else.

In summary, functions almost always compose, because they deal with values that have no life cycles.

What does composition give us?

Having simple core rules for composition gives us a better ability to take existing code apart and put it together in a different way, and thus become reusable. Objects were once hailed as something that finally brings reusability, but it’s actually functions that can achieve this much more easily.

The real key, I think, is that the composability afforded by the functional design approach means that the same approach can be used for both the highest levels of abstraction and the lowest level–behavior is described by functions all the way down (many machine instructions can also be represented as functions).

However, I think most programmers today (including me) don’t really understand how to make this approach work for complete programs. I would love for the functional programming advocates to put more effort into explaining how to build complete programs (that contain GUIs, database interactions and whatnot) in a functional way. I would really like to learn that rather than new abstractions from category theory — even if the latter can help with the former, show us OOP guys the big strategy before the the small tactics of getting there.

Even if we can’t do whole programs as functions, we can certainly do isolated parts of them. On the other hand, it seems that objects that put code close to the data help people make sense of a system. And if the objects are really designed to be composable, it works out quite nicely. I think a mix of object-oriented and functional programming such as in Scala, F# or even Java 8 is the way to go forward.

So, thanks for tuning in, and please leave comments below or find me on Twitter @t4ffer.

  • Oleg Šelajev

    On of the ways for learning about how to write large software systems in a functional way is to go through “Real World Haskell” book. It sits on my to-read list for a while now. If you know a better approach, please share.

    Another fun-trivia fact to ponder about is Clojure’s http library called httpkit. It seems pretty relevant, adopted and used in the community (I might be wrong here :P). So clojure is a lisp, right? Super functional and thus composable.

    If we check HttpKit’s github repo: https://github.com/http-kit/http-kit we can see that just a bit over 75% of the source code is java which is extremely OO and thus less composable. :)
    My take on that number is that it’s written in java and there’s a wrapper to expose it to clojure. Which actually proves nothing, but there should be reason that a library for such a functional and composable language as clojure is written in java. It might be purely performance though.

    So real-world systems would probably consist of smaller modules which are written using what suits their functional needs best and then wrapping them into a functional interface might help composeability and clarity.

  • Erik Post

    “On the other hand, it seems that objects that put code close to the data help people make sense of a system.”

    It sounds to me a bit as though you’re saying that this is more or less the last holdout, or at least your sweet spot, of OO. What I’d like know is in what sense you feel that does FP not put code “close” to the data. You tie code and data together using types, non?

  • http://ceylon-lang.org/blog/authors/gavin-king/ Gavin King

    “there isn’t even a commonly accepted definition of object orientation”

    Not true. Object orientation means the use of _subtype polymorphism_. That’s a perfectly well-founded notion, and perfectly distinguishes OO languages from languages which don’t support OO.

    “Do objects compose? The answer to that is complicated”

    I don’t see how the answer could possibly be complicated. Indeed, the question has an answer that is totally self-evident: _of course_ objects compose, since we can assemble programs out of them. We’ve been doing it on an industrial scale for three decades.

    “It’s so simple to understand compared to the numerous and perhaps even indescribable ad hoc compositions possible in OOP.”

    I have no clue what you’re talking about here. “Numerous”?? “Indescribable”?? I can think of two ways in which objects can be composed in OO languages: at the class level (inheritance), and at the instance level (references). I can’t imagine what all these other numerous forms of indescribable composition are. Care to enumerate some of them?

    “In summary, functions almost always compose, because they deal with values that have no life cycles.”

    I’m trying to understand what you’re trying to say here. Interpreted literally, it sounds like you’re saying that I can “almost always” take two arbitrary functions and compose them together to form something meaningful. But that’s just absurd, so that can’t be what you mean.

    Taken at face value, it’s simply not true that “functions almost always compose”.

    “I think a mix of object-oriented and functional programming such as in Scala, F# or even Java 8 is the way to go forward.”

    Again, I don’t understand what you mean by this. It seems to me that object-oriented programming is, and always has been, a superset of “functional programming” in the sense in which you seem to be using the term here. OO languages have always supported functional composition (one method calling another, or passing a reference to a method to another method) so I don’t know what would be new about this “mix”. Indeed, AFAICT, it’s impossible to design an OO language which doesn’t support functional composition and functional programming (again, in the sense you’re using the word here).

    P.S. Please don’t try to argue that a language doesn’t support functional composition if it doesn’t have function types and function references or anonymous functions. The “strategy” pattern is an ancient and well-known encoding of first-class function support into OO languages which don’t directly support first-class functions, and is commonly used by all OO developers I’ve ever met.

  • Erkki Lindpere

    I guess in OO it is just more natural to do so and indeed I can’t say that anything is preventing from doing that in FP. In OO it can also happen that people find ways to put code that depends on some nature of the data far from the data itself.

    E,g, in OO it is a bit more obvious from the structure e.g. dot and cross product operations would be defined within the structure of a 3D vector type. On the other hand operations involving multiple different types seem more natural to define in FP.

  • Erkki Lindpere

    Thanks for the comments, Gavin!

    “Object orientation means the use of _subtype polymorphism_.”

    I admit that subtype polymorphism is widely accepted as a defining feature of OO. There’s no *universally* accepted definition, but subtype polymorphism is quite *common*.

    “_of course_ objects compose, since we can assemble programs out of them.”

    The fact that we have been doing something for a period of time on a large scale doesn’t mean that what we’ve been doing is right or that we’ve been doing well.

    “I have no clue what you’re talking about here. “Numerous”??
    “Indescribable”?? I can think of two ways in which objects can be
    composed in OO languages: at the class level (inheritance), and at the
    instance level (references).”

    If in a pure functional language, you have functions f: A -> B and g: B -> C, you know that B is a value and it is “complete” (or consistent). Since g takes a B, you know you can pass f’s result to g, always.

    If in a OO language, you have a (possibly mutable) an object of type A and a method of some class f: A -> C, how do you know whether you can pass a given A that you have to the method f? You cannot know in the general case, because you don’t even know whether an object A is in a consistent state. Or maybe it’s a subtype that violates the Liskov Substitution Principle. And this is what makes it indescribable to me — for the composition to be safe may require that I call some other method on A first, before I can pass it around, or it may require god knows what other arcane knowledge for me to be able to use that object safely. OO languages don’t really provide many safeguards here, but pure FP languages eliminate this uncertainty about whether a particular composition is safe or not.

    Then again, OO languages can still do things to make composition a lot easier e.g. let me easily know that all A’s are immutable and there are no subtypes violating LSP, and I’ll know they are safe to pass around anywhere.

  • http://ceylon-lang.org/blog/authors/gavin-king/ Gavin King

    > If in a OO language, you have a (possibly mutable) an object of type A and a method of some class

    So, in fact, what you’re _actually_ talking about isn’t OO vs FP, it’s _imperative_ vs _declarative_. This is apparently a big source of confusion in the interwebs right now. There’s nothing, and I mean absolutely nothing, in any definition of OO which talks about mutation. Unfortunately some folks seem to have recently got the impression that there is.

    My speculation is that this is due to a confusion over two different senses of the word “state”. When we say that an object has state we mean that instances of its class are distinguishable by the references they hold, _not_ that these references are necessarily _mutable_. An immutable object is just as objecty as a mutable object.

    To be clear, objects don’t rob you of referential transparency, and functions don’t protect you from the loss of it. Just like there are:

    – mutable objects, and
    – immutable objects,

    there are:

    – impure functions, and
    – pure functions.

    If you program using impure functions, as most of the world outside of some very particular FP communities does, you’re just as vulnerable to the problems you’ve been trying to blame objects for.

    > If in a pure functional language, you have functions f: A -> B and
    > g: B -> C, you know that B is a value and it is “complete” (or
    > consistent). Since g takes a B, you know you can pass f’s result
    > to g, always.

    Object orientation doesn’t rob you of the ability to compose functions (or methods). In Ceylon, I can write:

    value writeFormatted = compose(process.writeLine, dateFormat.format);
    writeFormatted(todaysDate);

    Here, “process” and “dateFormat” are objects. But I easily composed their methods just like you can compose ordinary functions.

    It’s true that classes don’t exist at the same level of granularity as functions/method, and thus they’re less likely to compose “by accident” rather than “by design”, but I’m not sure what that proves. You still have the function level of granularity to work with when you need it.

    You see, there are multiple levels of granularity in modern languages: function, class, namespace, module. It’s important to understand their different roles. The arguments you’ve just given me for objects not composing could equally be applied to modules.

  • Erkki Lindpere

    Yeah, basically I admit that the definition of OOP (if there is one true definition) doesn’t require mutable state. I was also just reading the reddit comments and yeah, I more tend to think that “state” usually means “mutable” and “immutable” means “stateless”, but I’m willing to accept existence of “immutable state”.

    However, I think the way OO programs are structured in most modern OO languages seem to make using mutable state a much easier solution for problems approaching some complexity than alternatives.

    I agree about different abstractions at different granularity (maybe that was forgotten a bit in the original post), and I wish the programming paradigm (or language) would help choosing appropriate abstractions for appropriate problem granularity. For example, implementing some subsystem as pure functional, with immutable values/objects, and being able to assert that it’s so in code, then exposing all of that subsystem as an object. It would be great if the language could nudge me towards picking the right kind of abstractions by accident so that what I write without thinking would be easily composable.

  • http://ceylon-lang.org/blog/authors/gavin-king/ Gavin King

    Right, so some OO languages _do_ nudge you towards using more immutable objects. Sure, Java/C#/Ruby don’t really do much to encourage you to use immutable objects, just like Fortran/Pascal/Perl don’t nudge you towards using pure functions. But OCaml/F# and even Ceylon and Scala *do* (to greater or lesser extents). In Ceylon you have to explicitly annotate mutable references, because they’re considered slightly discouraged.

    Which is what irritates me about this whole line of argument: OO != Java/C#/Ruby. There are other OO languages out there. Folks bashing on OO just need to get out more.

  • Ivano Pagano

    @gavinking:disqus
    “Not true. Object orientation means the use of _subtype polymorphism_. That’s a perfectly well-founded notion, and perfectly distinguishes OO languages from languages which don’t support OO.”

    This exemplifies exactly what you’re trying to disprove, Gavin, since not everyone would agree with this definition, me for a starter.
    As far as I’m concerned I would say that what best defines OO (as programming with objects) is the use of objects as units of cohesion for access to state and behaviour, with the added feature of abstracting over the inner working through public interfaces.
    I see subtyping as an added feature over encapsulation.
    YMMV.

    @erkkilindpere:disqus
    Just as you did I was evaluating lately how to best use compositional features for both aspects of a FOOP language and what tools are currently available in existing (mainstream) languages.
    My (absolutely personal) considerations would need some more space and maybe I’ll blog a post and link it here in the future.

    good day to everyone

  • http://ceylon-lang.org/blog/authors/gavin-king/ Gavin King

    “I would say that what best defines OO (as programming with objects) is the use of objects as units of cohesion for access to state and behaviour, with the added feature of abstracting over the inner working through public interfaces.”

    1. I can see two senses in which you might talk about “objects as units of cohesion”. Let’s consider each in turn:

    – One is that you have language level visibility rules to control access to “private” members of an instance or class. But not all languages that are commonly considered to support object-oriented programming have this. Indeed, there are OO languages which don’t support data hiding at all.

    – On the other hand, perhaps you mean “cohesion” in a weaker sense here, where you’re not really thinking of visibility control, but just the ability to package functions and values together into an “object”. But plenty of functional languages which are *not* commonly considered OO support tuples and even records.

    (OK, OK, in fairness, some people suggest that it’s the added ability to do “open recursion” which makes all the difference between “objects” and “record types”, but that’s a highly technical distinction, and one most programmers are only dimly aware of. I would not be happy with any definition of OO that talks about open recursion as being the big distinguishing feature.)

    So the first part of your definition doesn’t really much help us to clearly distinguish “OO” programming. So let’s now turn to the second part of your definition.

    2. “the added feature of abstracting over the inner working through public interfaces” is, literally, subtype polymorphism, which is exactly my definition.

    *Every* language I know of that is commonly considered to be object oriented has some kind of subtype polymorphism, whether via dynamic typing, structural typing, or nominal typing. Conversely, no language I know of that is _not_ considered OO _does_ have subtype polymorphism.

  • Ivano Pagano

    Well said.

    Probably I was interpreting the discussion while focusing more on object-oriented programming (intending a way to design code) as opposed to object-oriented languages (intending features supported by syntax and semantics).
    From a language-centered perspective your position is correct. From a design-centered perspective I agree with you that the distinction between languages becomes blurry, and this was what I was talking about.

    About your point 2. above, regarding information hiding or encapsulation, I’d say that subtype polymorphism corresponds to the definition only when we talk about statically typed languages, but I may be mistaken here.

  • http://ceylon-lang.org/blog/authors/gavin-king/ Gavin King

    Well to me any kind of dynamic dispatch is a form of “subtype polymorphism”, whether it is in a dynamically-typed language like Smalltalk or Ruby, or a statically typed language like Java, Go, or C++. I think it would be pretty strange to say that Smalltalk doesn’t have subtype polymorphism just because it doesn’t have static types!

  • Ivano Pagano

    “I think it would be pretty strange to say that Smalltalk doesn’t have subtype polymorphism just because it doesn’t have static types!”

    Touchee’

  • cosmin

    you should have added C# on the list in the final paragraph. C# supports functional constructs since C# 3.0. F# borrowed linq from C# just like C# will get many of F# features in its future releases.

    I’m not a C# fanboy, but could not stand having newly born java 8 and omitting C# :)

  • Broc kelley

    good god, i wish I could keep up with anythign any of youa r saying.