Functional Programming (FP) has been around just as long if not longer than Object-Oriented Programming (OOP), but it’s only (relatively) recently gaining in popularity, particularly in the JavaScript community. Why?

I went to MIT in the early 00’s. Structure and Interpretation of Computer Programs (or SICP - sick-pee) was my textbook. So my first formally-taught programming language was mostly functional. Then I went on to work in industry for over a decade and never had to think much about FP. Now I’m shocked to learn that the book from college I didn’t pay much mind is considered the functional programming bible.

It’s a good textbook. I’m sure it made me a better overall programmer. But it wasn’t something I applied very often early in my Java - or ActionScript, Ruby, or JavaScript - career.

Then I taught at Wyncode Academy for four years and found myself having to explain FP concepts to newcomers. It’s hard - much harder than OOP.

Why is FP so much harder than OOP? Related question: why has it taken so long for FP to “catch on”? We in the coding community really need to grapple with why FP is so hard to teach. Evangelizing that FP is the “one truth path to enlightenment” is repeating the same mistakes that caused FP to languish in the industry for so long.

Introductions to FP are missing something. It’s not just a new style of programming. It’s a new way of thinking. We need to ease ourselves into this new paradigm for the sake of newcomers and experienced programmers with OOP-dominant backgrounds. One of the techniques I used at Wyncode to getting a running start into a hard concept is storytelling. If I can get my students to understand the context - the big picture, it’s easier to explain the technical details.

The Big Picture: History

To start, we need to go back to the beginning. How does a computer work?

The most common (popular? easy-to-grok?) model of computing is the Turing Machine. The state that FP programmers tend to complain about is staring you right in the face in the Turing machine. An algorithm for operating this machine represents transitions between different states - from these boxes being on to these other boxes being on. If you try to imagine two machines operating on the same section of tape at the same time, you can begin to understand why concurrency in OOP is hard. But that’s another story. The Turing Machine is a universal machine. It can be used to solve every solvable math and logic problem. This simple collection of operations - move left, move right, write a dot, read a dot, erase a dot - are enough (given enough time and resources) to solve every math problem in the universe. That’s what Alan Turing proved in 1936.

In many ways, this is how a comptuter “works”. But this is also how a computer works.

An Adder Circuit

This is a circuit for adding numbers together. It’s the kind of thing you’d find inside the CPU of a computer.

This is not a Turing Machine. It’s not universal. It’s just addition.

There’s also no Turing-machine-like “state”. You apple voltage to the inputs corresponding to the numbers you want to add and you detect voltage in the outputs corresponding to the sum. As soon as you turn off the voltage, the answer goes away. There’s no “tape” sitting around to read or manipulate. You can’t have two circuits operating the same logic gates simultaneously. This circuit is also fast. While a classic Turing machine flips 1s and 0s back-and-forth on some medium, this circuit operates at the speed of electrons through a wire. There are no “moving parts”.

This is a different model of computation. Each of the logic gates (AND, NAND, OR, XOR) are pure functions. They accept inputs and produce outputs with no side-effects. And if all you have is the ability to create these “functions”, you can also solve every math problem in the universe. That’s what Alonzo Church proved, also in 1936.

So we’ve got two different models of computing: the Turing machine using little boxes of 0s and 1s (objects) and Alonzo’s Church’s lambda calculus built out of logic gates (functions). Which one is correct?

For a time there was a debate about whether the abstract Turing machine could solve the same set of math problems as lamba calculus (and vice versa). Eventually they were proven to be equivalent.

Being equivalent means that they’re equally powerful. Any algorithm you could write for a Turing machine, you could also write using functions. That implies that any program that can be written in Turing machine software can also be represented in hardware. What does it mean to program “in hardware”?

We can see “hardware programming” represented by Application-specific Integrated Circuits (ASICs). You can create a circuit “programmed” to do one thing, like mine Bitcoin or play chess. Since discovery of the Church-Turing Thesis, we’ve had two options for programming. Hardware is fast and software is slow(er). If you make a mistake in your software, you hit the delete key and try again. If you make a mistake in your hardware, you’ll have to grab your soldering iron. It’s a classic engineering design trade-off.

I suspect, based on my anecdotal experience as a former instructor, that people comfortable with hardware (electrical engineers) tend to grok FP concepts more easily than other folks.

So let’s say you have an algorithm written in an OOP-style and you’d like to translate into an ASIC. You could try building it from scratch, but it’s better to rewrite the program into a more FP style so it maps better to the hardware domain. And most programming languages are flexible enough to do that, but some are better than others.

FP-tilted languages tend to look like circuits. Specifically, the “pipe operators” in Unix, Elixir, F#, JavaScript (maybe someday) and others make your code look like a circuit diagram: inputs go into the left, flow through a number of “gates” (pipes) until they’re transformed into the final output on the right. It’s an interesting coincidence that the pipe operator used by some languages ( >) looks like a logic gate.

A good “big picture” way of introducing FP is to start by talking about how circuits work, how they can be “programmed”, and how you’d model circuit diagrams in code. The Big Picture: Philosophy

I picked up a Philosophy minor with my CS degree, so one of the things I’m fascinated by is the intersection between those two fields of study. I find talking about the overlap helpful when teaching new coders, particularly those with Humanities backgrounds.

One of the philosophically important concepts of FP is the concept of “functional equivalence”. Perhaps the best example demonstrating this equivalence is Tom Stuart’s great article

“Programming From Nothing”. Stuart demonstrates how numbers can be represented by their Church encoding. HELLO = ->() { puts "hello" }

ZERO = -> (func) {

does nothing

func }

same as: ZERO.call(HELLO)

ZERO[HELLO] # nothing

ONE = -> (func) {

calls it once

func[]; func }

ONE[HELLO] # one "hello" displayed

TWO = -> (func) {

calls it twice

func[] func[] func }

TWO[HELLO] # "hello" twice

convert number function

into number object

def to_integer(func)

count how many times counter's called

n = 0 counter = ->() { n += 1 } func[counter] n end

p to_integer(ZERO) # 0

p to_integer(ONE) # 1

p to_integer(TWO) # 2

We have one (the object) and one (the function). They are both “functionally equivalent” to each other.