What does "functional programming" mean to you?

Andrew (he/him) - Nov 18 '21 - - Dev Community

Photo by Magda Ehlers from Pexels

What crosses your mind when you hear the phrase "functional programming"? Some people imagine arcane, esoteric code, built around functors and monads and applicatives, but I think of

  1. immutability and referential transparency

    Immutability just means you create new objects instead of mutating (changing) old ones. This means every variable is effectively final or const or whatever your language calls it. Instead of

    public class Dog {
      public final String name;
    
      public Dog(String name) {
        this.name = name;
      }
    
      public void rename(String newName) {
        this.name = newName;
      }
    }
    


    your renamed Dog would be a new object, à la

    public class Dog {
      public final String name;
    
      public Dog(String name) {
        this.name = name;
      }
    
      public Dog rename(String newName) {
        return new Dog(newName);
      }
    }
    


    Immutability makes it easy to reason about objects (Dog doug = new Dog("doug") will always have name.equals("doug")), but it can make your program less efficient, both in terms of memory usage and in terms of performance -- always creating new objects takes more time and space than reusing existing ones.

    Referential transparency is a closely-related concept. In a nutshell, what it means is that -- wherever you have the value x in your code, you can replace it with whatever you initially declared x to be. So if const x = 10, then x always equals 10. You can replace every instance of x with the literal 10 and your program should do the exact same thing. (This does not work, of course, if your objects and variables are mutable and you can do something like x = x + 1!)

  2. higher-order functions

    In many languages, functions and objects are separate things: you can pass objects as arguments to functions, and functions can be scoped as object methods. But languages which encourage a functional style will allow you to pass functions as arguments to functions, like in Scala

    def process(text: String, fs: (String => Unit)*): Unit = {
      fs.foreach(f => f(text))
    }
    
    process("Hello, World!", println)
    


    The above process method takes a String argument text and a (String => Unit)* argument fs -- that is, zero or more functions which themselves take a String and return Unit, Scala's equivalent of void. Once you get used to working with higher-order functions, their utility is immense

    import Console._
    process("this does nothing")
    process("print to stdout", out.println)
    process("print to stderr", err.println)
    process("print to both", out.println, err.println)
    process("etc", out.println, err.println, myLogFunc)
    


    Adding a new output device is as easy as passing a new function to process. Treating functions as "first-class citizens" of a language opens a huge array of new possibilities.

  3. pure functions (side-effect-free functions)

    A "pure" function is a function without "side effects". What does that mean? In a nutshell, it means that a function should behave similar to a mathematical function (think y = f(x)) -- it should take some value, x, and return or become some other value, y.

    Mathematical functions don't write to log files or print text to a terminal or change the value of other variables on the page -- they turn x into y, that's it.

    Of course, computer programs are kind of useless if they can't take input from a user, or read a config file, or communicate with external resources. Input / Output, or IO, is required to make programs "interactive". Functions which perform output (typically returning no value) typically return a void or Unit or null value of some kind. Haskell, for instance, uses its IO monad for input and output.

    We can't eliminate side-effecting functions, but we should try to make it as clear as possible what the intent of our functions are. If you want to calculate a value, and log it to a file, separate those concerns into two separate routines, if possible.

  4. declarative programming and lambdas

    "Functional programming" also makes me think of a particular style of programming: iterating over data structures (like arrays, dictionaries, maps, sets, etc.) using declarative-style functions like map, filter, foreach, and so on.

    Imperative programming is (in a sense) the opposite of declarative programming. An imperative program declares exactly what is to be done:

    int[10] array = ...
    double sum = 0;
    int ii = 0;
    for (ii = 0; ii < 10; ++ii) {
        sum += array[ii];
    }
    


    A declarative program specifies what is to be done, and the language itself figures out how to do that. For example, in a declarative program, we might rewrite the above as

    var sum: Double = 0
    array.foreach(x => sum += x) // lambda
    


    ...or

    val sum = array.fold((partialSum, x) => partialSum += x)
    


    ...or even just array.sum. Functional programming also tends to make heavy use of "lambda" functions, like the one used above, x => sum += x. This is an anonymous function (it doesn't have a name like addToSum) which takes a value x and adds it to the value sum.

    Declarative programming, and lambda functions, are hallmarks of functional programming, in my view.

  5. stream processing

    All of the above leads me to routinely return to the (not at all novel) metaphor of plumbing for functional programming. You put data in one end of a "processing pipeline", like water entering a pipe. It may be split into multiple streams, sent down different paths, siphoned off into some database, or dumped ultimately into some data lake, but the data moves from producers / sources (input), through flows / transformers / conductors, and into consumers / sinks.

    Stream processing is quite similar to reactive programming, which has a whole manifesto and is implemented in several languages (here's one for Scala, and another for R).

    Remember:

    "The Internet is... a series of tubes."
    -- U.S. Senator Ted Stevens [source]


Wikipedia also mentions lazy evaluation and recursion, but these don't immediately jump into my mind as "pillars" of functional programming.

How about you? What comes to your mind when someone says "functional programming"?

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .