Two Worlds: Functional Thinking after OO

It goes without saying that functional programming is very different from object-oriented. While it may take a while to grasp, it turns out that functional programming can also lead to simpler, more robust, maintainable and testable solutions.

The First Encounter

In his classic “Clean Code” Robert C. Martin describes a great (and widely adopted) way to object oriented programming. He says code should read from top to bottom like prose. Start with the most high-level concepts and break them down into lower-level pieces. Then when you work with such a source file, you may be able to more easily follow the flow and understand how it works.

It occurred to me it’s quite different from how the natural flow of functional programming in Clojure, where typically all users are below their dependencies. I was unsure if (and how) Uncle Bob’s ideas apply on this ground. When I tweeted my doubts, his answer was: “You can forward declare”. I can, but still I was not convinced.

The light dawned when I came across Paul Graham’s essay titled “Programming Bottom-Up”. The way to go in functional programming is bottom-up, not just top-down.

Language for the Problem

Lisp (and Clojure) have minimal syntax. Not much more than parenthesis. When you look at Clojure source, it’s really dense because there is no syntax obscuring the flow. What you can see at the bottom of the file, though, appears to be program in language created just for this problem.

As Paul writes, “you don’t just write your program down toward the language, you also build the language up toward your program. As you’re writing a program you may think +I wish Lisp had such-and-such an operator.+ So you go and write it. Afterward you realize that using the new operator would simplify the design of another part of the program, and so on.”

Simplicity and Abstractness

Ending up with a language tailored for the problem is not the only nice feature of functional programming. Compared to object-oriented, functional code tends to be a lot more abstract. There are no intricate nets of heavy stateful objects that very often can’t talk to each other without adaptation and need a lot of care to weave together.

Data structures are very simple (if not minimalistic), what makes them easy to use with more functions. They also are immutable, so there is no risk of unexpected side effects. On the other hand, because of simplicity of data structures functions turn out to be much more abstract and generic, and hence applicable in broader context. Add closures and higher-order functions and get a very powerful engine with unlimited applications. Think how much you can do with map alone – and why.

Another way to look at code organization is layers or levels of abstraction. In object-oriented, it usually means that a bottom layer consists of objects which provide some functionality to the higher level. Functional programming takes it one step further: “[each layer acts] as a sort of programming language for the one above.” And if there is a need for distinction to layers, it’s only because of levels of abstraction. Much rarelier because of incompatibility and never because of handcuffing encapsulation.

Maintainability and Code Organization

We all know that in object-oriented programming most of the time you should start with the purpose at hand and go for a very concrete, specialized design. “Use before reuse” is the phrase term here. Then, when you need more flexibility you decompose and make the code more abstract. Sometimes it can be a difficult, time-consuming and bug-inducing effort. It’s not the case with functional programming. “Working bottom-up is also the best way to get reusable software. The essence of writing reusable software is to separate the general from the specific, and bottom-up programming inherently creates such a separation.”

Compared to OO, refactoring of functional code is trivial. There is no state, dependencies or interactions to worry about. No way to induce unexpected side effects. Each function is a simple construct with well-defined outcome given the direct input. Thanks to that, it’s also much easier to test (and cover with automated test suites).

The Curse of Structured Programming

Object-oriented originated from structured programming and all developers inevitably have such background. It takes some training to learn how not to write 2,000-line classes and 500-line methods. It takes much more to learn how to avoid inheritance, program to an interface, compose your code of smaller units and cover them (and their interactions) with tests.

There are ways to make object-oriented programs more likely to succeed, have fewer bugs and be more maintainable. A ton of books has been written on this, including but not limited to excellent and invaluable works by Robert C. Martin, Martin Fowler, Eric Evans, and so on, and so forth. That’s a lot of prose! It turns out that object-oriented programming actually is very difficult and needs a lot craftsmanship and attention.

Functional programming is free from many of these issues. What’s more, learning it yields great return to an object-oriented programmer. It can teach you a lot about algebraic thinking, but also breaking code down into smaller pieces, intention-revealing names and interfaces, avoiding side effects and probably many other aspects.

26 thoughts on “Two Worlds: Functional Thinking after OO

  1. If you’re writing 2000-line classes and 500-line methods then you’re in no way qualified to be speaking about object-oriented programming, let alone comparing it with the latest FAD ideology.

  2. A lot of the heavy class stuff you describe is what happens in Java land. If you were using Ruby or Python you wouldn’t think that way.

    OO doesn’t have to be heavy, but the descendants of C++ make it so.

  3. @Mark Lee Smith: With the amount of functional concepts seeping into OO languages like Java and C# you ought to be ashamed of ignorant comments like that. And in fairness, its possible to write monolithic code regardless of your choice of programming paradigm. Besides a somewhat bad example which you are now nitpicking, what he says is correct: when you get simpler data structures, it becomes easier to break away from sub-type polymorphism which is problematic to say the least. If you don’t recognize that, then perhaps you have not spent enough time doing OOP. If I have to use another convoluted design pattern to do something that is supposed to be /simple/ then I will blow my brains out. I am so sick of this C++ heritage that it makes me want to puke – it has truly reached the height of its usefulness and its plain to see that modern languages are shifting away from this crippling ideology. K THX BYE.

  4. Mmmm, I suspect that the 2000 line classes and 500 line methods is the worst case in a ‘heard it from a friend who’s uncle used to work with someone…’ kind of way. Although I’m pretty sure it does happen it’s probably not going to happen to you.

    Looking at the projects that I have the stats for the 23 active project where I work we have around 6 methods to a class and somewhere between 6-28 lines of code per method. Worst case is 50 lines to a method.

    I suspect that it depends on the language, this was Ruby, not sure what the stats would look like for Java or C++.

  5. I always find it amusing when developers call functional programming a fad as if it hasn’t been around since, well, forever. Thanks for the good read.

  6. @Peter Hickman:

    It’s happened to me many times. Currently on a code base approx 500k LOC. I’ve seen a class near 10k loc, and quite a few methods in excess of 500 lines… can’t remember the record holder, but it’s over 1000. This is for a 20 year old inventory management system.

  7. 2000 line 500 method? It does happen. At my current job we’re in the process of re-designing a website, written in asp.net, where there are numerous classes with 1000+ LOC and more than a few methods with 500+ lines. The guys who designed that are always talking OO and such.

    I agree that OO can easily be abused compared to a functional approach.

  8. @Peter Hickman:

    I too can vouch for their existence. I am currently working on a project with a class ~10k lines of code with many functions ranging in the 100-600 line range. Granted that’s the worst of them – but there are many of the others are in the 1000+ range with several hundred line methods.

  9. The real pain of most OO languages comes from having to make a new class to do anything. 1000 line methods become common when you have to make another stupid class full of boiler plate, in a different scope, maybe in a different file, implementing some stupidly verbose interface, in order to get anything more abstract than for loops and conditionals.

  10. @mark,

    you say that like you were expelled from the womb as programmer extraordinare all your code clean and as perfect as can be. I don’t know that I have ever written a 2000 line class but I know that there are many things I once did that I would not even consider doing now. I for one am glad to see these functional approaches gain prominence if for no other reason to make me reevaluate how I code so that hopefully I can find better, cleaner, and faster ways of doing things.

  11. @John @Even @Magato and friends: The irony is that I was a very big advocate of functional programming about 6 years ago. Unfortunately I lost the faith or I might be considered rather cool now right ;).

    IMO functional programming offers very little over higher-order procedural programming (both procedural and object-oriented languages have had things anonymous functions, closures and powerful type-systems since the 70s!)

    All this talk about bottom-up programming is fine and dandy but it doesn’t just apply to functional programming languages! Likewise decomposing difficult problems into simpler ones isn’t in any way restricted to functional programming.

    From the outside it’s like you’re finally discovering good software design practices, and after years of hacking object-oriented programs together without much thought, you’re looking for something to blame.

    [Admittedly mainstream object-oriented language like C++ and Java have a lot of problems, but if you dig around a bit you’ll find that many of these problems aren’t inherent in object-oriented programming, and may have been solved for decades e.g. multiple-inheritance is a solved problem when approached using one of the many composite-inheritance mechanisms that you’ve probably never heard of.]

    In a couple of years time when you guys have actually written a few non-trivial functional programs you’ll feel differently.

    In my experience functional programs quickly devolve all to quickly into a hive of thousands of tightly coupled functions (often with cryptic single-letter names, but that’s not limited to functional programming).

    Don’t get me wrong, I have nothing against functional programming, and I’d encourage everyone to learn what they can… just try not to go around evangelising before you really understand a thing. You just might end up feeling like a fool.

  12. “Ending up with a language tailored for the problem is not the only nice feature of functional programming”

    Also not just a feature of FP. Just because you speak in verbs and I speak in nouns doesn’t mean they’re not both languages.

  13. Guess it’s time to go learn functional programming… closures rock my world but I guess I need the bigger picture.

  14. Somewhat on this subject, does anyone know of a good book pertaining to the practice of programming/design issues that is also geared towards functional programming in particular? I’m talking about books like the ones you mention by Fowler, Martin, Evans, etc. Or if none exist, which is the most general of these that could best be applied to functional programming?

  15. @Beau Fabry: I would say that when you are focused on the nouns you have to worry more about making sure the noun can do everything in your paragraph. Focusing in on the verbs may mean that you are more likely to say “yeah, that can bark”.

    That may get interpreted strangely, but I’m trying to point out that it’s a different point of view. Although I would imagine great results are possible from either approach, you’ll likely have a different mentality which will have it’s own side effects.

    I would imagine that partial application, real lazy evaluation, higher order functions, and closures are quite the enablers. I have found uses for the FP features creeping/existing in my every day languages.

  16. I use FP style on mixed-paradigm languages a lot, and in fact all my current favourite languages support FP – Scala, F#, Javascript – which can be viewed as a verbose version of Lisp.

    The difference between them and a “pure” FP language like Haskell? You get work done quicker because the designers of these languages don’t classify simple IO like printing to screen and saving a file as “undesirable side-effects that needs to be eliminated from a function, or worked around like Haskell does with Monads”.

    Half of all the benefits provided by FP can be realised on OO/imperative languages by making things immutable anyway – const/final everything in C++/Java, eliminate mutator methods.

    The other half, provided by lambda and higher order functions, can most of the time be worked around. Easier (but still kludgy) with powerful languages like C++ with function pointers, functors, and C++0x lambda expressions, less easily with lesser languages like Java with Interfaces, but these workarounds 90% of the time get things done, and this 90% becomes 99% when one allows a limited degree of mutability.

    That’s a reason why the mainstream languages become mainstream – they make simple things, which is what 80% of people do 80% of the time, trivial.

  17. @Michael if it was all about easy and getting things done then Python would have replaced Java and C# awhile ago. Mainstream is about being safe (mostly for the business). Also you aren’t considering that the big draw of Haskell is it’s legendary type system.

    I have never liked the mentality of “I can basically do that now, so why should I learn another language” (NOT saying Michael intended this, but I have come across this). Trying to force more lambda calculus out of your Java is probably a good reason to check out Clojure or something. It’s not just about expressing the concepts, but how it’s expressed and how easily you can use them. I also hate when someone says that a language is only syntax and they could quickly learn any.

    Anyway, when it’s expensive for the programmer to do something, they are more likely to isolate it out or avoid it. When it’s easy to do something they are more likely to scatter it about. Discipline and experience could bridge those, but there are reasons behind language design.

  18. People who think that FP is FAD just don’t understand what FP is. In simple sense FP is a “way to think” about programming / problem solving. If you have been doing imperative programming for your whole life till now, your neurons are so accustomed to the imperative way of thinking that learning the FP thinking will make your brain hurt and you will call it a FAD. Those who can over come this mental block and able to adapt and practice FP thinking knows what FP can do for your programs.

    Remember that FP is not something magical, you can still write pathetic code in FP as you can do in OO languages.

    One other comment is that decomposing problem into smaller problems is not specific to FP, I agree, BUT the decomposition depends on the “glue to compose”. It wont make sense to decompose a problem and later found that composing the solution is a hell of a task. This “glue to compose” is powerful in FP as compared to imperative languages.

    Lets talk about State now. Its a fact that you cannot write a useful program without some sort of State. FP (as in my sense of FP) says that make your state explicit (rather than implicit as in OO/imperative). As par FP you have to have strict control on your state and it should have very small exposed surface area where as in OO/imperative the state is all over the place which leads to other serious problems.

    FP was developed as an abstraction first and then implementation where as imperative was implementation first and then we will create abstraction on top. In early days we don’t have such powerful tech and hardware to support FP abstraction and thats why it has not been so much popular in past BUT now the tech is mature and hardware is damn powerful. It is the time to get benefit out of FP “thinking”.

    I hope this can help who think FP is a FAD :)

  19. Konrad,

    you said: “Object-oriented originated from structured programming and all developers inevitably have such background.”

    What do you mean by structured programming? Here is Steve McConnel’s definition in Code Complete:

    The term ?structured programming? originated in a landmark paper, ?Structured Programming,? presented by Edsger Dijkstra at the 1969 NATO conference on software engineering (Dijkstra 1969). By the time structured programming came and went, the term ?structured? had been applied to every software-development activity, including structured analysis, structured design, and structured goofing off. The various structured methodologies weren’t joined by any common thread except that they were all created at a time when the word ?structured? gave them extra cachet.

    The core of structured programming is the simple idea that a program should use only one-in, oneout control constructs (also called single-entry, single-exit control constructs). A one-in, one-out
    control construct is a block of code that has only one place it can start and only one place it can end. It has no other entries or exits. Structured programming isn’t the same as structured, top-down design. It applies only at the detailed coding level.

    A structured program progresses in an orderly, disciplined way, rather than jumping around unpredictably. You can read it from top to bottom, and it executes in much the same way. Less disciplined approaches result in source code that provides a less meaningful, less readable picture of how a program executes in the machine. Less readability means less understanding and, ultimately, lower program quality.

    The central concepts of structured programming are still useful today and apply to considerations in using break, continue, throw, catch , return, and other topics.

  20. I will vouch for obese objects. In 2006 I worked at a web design firm that had built its own framework, which it used on all projects. When I was hired, I needed to learn that framework and use it on all the sites that I built while I was there.

    The framework had been developed during the 1990s and written in Perl, and then in the early 00s they ported it to PHP. By the far, the worst part of this framework were the classes that wrapped data in HTML. I recall a class devoted to wrapping data in an HTML table. The main method of the class had over 1,100 lines of code. That was 1 method.

    There was some talk of re-factoring it, but the management felt that the code worked well and therefore re-factoring was not needed. Time spent re-factoring would be time we could not bill for, and so we might as well just keeping working on client’s projects, which provided us with work we could bill for.

    I suspect there are many small shops out there with internally built frameworks that have bloated code like that. I suspect the amount of bad code out there is easy to underestimate.

  21. > just try not to go around evangelising before you really
    > understand a thing. You just might end up feeling like a fool.

    What about posting a comment on a blog post that you clearly don’t understand? How does that feel?

Leave a Reply

Your email address will not be published. Required fields are marked *

Spam protection by WP Captcha-Free