Friday, October 30, 2020

Why I prefer functional programming

functional

This post explains why I stick with functional programming, using a rationale that a non-functional programmer can relate to.

The reason is actually pretty simple: functional programming idioms are more enduring and portable than idioms from other programming paradigms (such as procedural or object-oriented programming). To explain why, I need to first define what I understand “functional programming” to mean (which is admittedly an imprecise and vague term).

I personally use the term “functional programming” to denote a style of programming where you restrict yourself as much as possible to the following language features:

  • Scalars, including:
    • Numbers
    • Strings
  • Algebraic data types, including:
    • Records
    • Tagged unions, including:
      • Bools
      • Optional values
      • Enums
    • Recursion, including:
      • Lists
  • First-class functions

Carefully note what’s absent from the list. We don’t mention:

  • Classes / Objects
  • Mutation
  • Structured programming idioms (e.g. for / while loops)

That’s not to say that those features are banned from my definition of functional programming. Think of the definition as more of a “tech radar” where the former set of features fall in the “Adopt” category and the latter set of features fall in the “Hold” category.

So what distinguishes the former “approved” features from the latter “discouraged” features? The approved language features are “timeless”. You’re always going to need numbers, lists, strings, functions, records, etc. They aren’t even specific to programming: they predate programming and originate from good old-fashioned math. If your language doesn’t support one or more of those features you will run into difficulties modeling some problem domains.

However, once you verse yourself in functional programming idioms you realize that you don’t actually need much else beyond those features:

  • Error handling? Use a tagged union (e.g. Either / Result)
  • Loops? Use recursion
  • Dependency injection? Use a higher-order function

When you view things in that light you begin to view other programming idioms as window dressing that comes and goes; not fundamental to the discipline of software engineering.

Moreover, using “timeless” primitives fosters a programming style that is more portable than most. Most functional programming idioms can be ported to any language, with the notable exception of recursion and generalized tagged unions (which not all languages support). However, functional programmers learn how to translate recursion and tagged unions to equivalent idioms in other languages (e.g. loops and the visitor pattern, respectively). However, if you try to port object-oriented idioms to a non-object-oriented language you’re going to have a bad time — likewise for porting imperative idioms to a functional programming language.

This is why my tech radar marks functional programming as “Adopt” and marks other programming paradigms as “Hold”.

7 comments:

  1. Hey! I’m just wondering: didn’t you have a situation when you need OOP? I.e. did you work on any CRM or CMS, or ORM?

    ReplyDelete
    Replies
    1. You can use ORM with functional programming languages, for example Haskell has esqueleto.

      Delete
    2. I'm thinking that a lot of places where you might think you want objects/classes you would use records instead.

      Delete
    3. I understand that this avoids the question, but another common approach is to simply put bound SQL queries behind functions and avoiding an ORM entirely. I see this frequently in Lisp, especially Clojure where the JDBC is often used.

      Delete
  2. The problem is the problem space is vast and resources are limited, so many issues regarding software engineering, don't really have anything to do with software engineering but are about human frailties, aka say we one day invent AI that programs better then the best programmers. At that point we are just reaching the cognitive limits of the human brain.

    So I really hate this "one size fits all approach" there is layer upon layer of abstraction that we are inheriting from von neuman architectures past, aka programming languages and programming in general is chained to a certain conception of computation and it's expression.

    The reality is there are fundamental research questions and innovations that are deep and that go beyond one humans lifetime. So when someone says something is "impossible" or "these truths are immutable" they are usually wrong because future paradigms of computation and programming haven't been invented yet.

    I have serious doubts about my own knowledge of how to design and engineer abstractions based on the abstractions of other people like hardware engineers... so I have serious doubts the human brain can possibly grok it all in a mere 80 years of lifespan.

    Cautious doubt is the best strategy and we should assume we are mostly ignorant and incompetent because the problem space of nature is vast and infinite.

    ReplyDelete
  3. In python code, I have seen: a().b().c().d().e().f().g()
    Ooops. something blows up. Where did it fail. This is horrifically bad and unsupportable code.
    So why is the totally functional approach 'better'?
    Any time you have to interact with a network/filesystem, things CAN! and WILL! occasionally file

    ReplyDelete
    Replies
    1. Its all the more important to separate functions with side effects (network and filesystem) and the ones that are pure, Pure FP languages (Haskell) provide great semantics to model such things, such a construct forces developers to consider what to do with failures from the word do...

      Delete