Functional Programming

Functional programming treats computation as the evaluation of mathematical functions, and avoids changing state and mutable data. It emphasizes applying functions, in contrast to structured and object-oriented programming's emphasis on changing state step by step.

Key Concepts

  • Function — a mathematical expression that takes one or more inputs and produces one output.
  • Pure function — a function with no side effects: the same input always produces the same output.
  • Immutable data — data that cannot be changed once created, which makes programs easier to reason about and to test.
  • Lazy evaluation — evaluating an expression only when its value is actually needed, which can improve performance.
  • Higher-order function — a function that takes another function as an argument, or returns one.
  • Closure — a function defined inside another function; closures are the instances that higher-order functions create.
  • Callback — a function passed as an argument and invoked later by the function it was passed to. The lexical context — the local scope bound to a closure — is what lets that callback still see the variables around where it was defined.
  • Generator — a function that yields a new value on each call; useful for producing IDs, elements of a set, or driving an iteration.
  • Recursive function — a function that calls itself with different arguments; tail-call optimization is the technique some runtimes use to make deep recursion cheap.
  • Lambda — a pure, deterministic function expressed as a single expression rather than a named declaration. ("Deterministic" here means the same input always gives the same output — the opposite of a stochastic function, which returns a random result.)

Function

A function is a named block of code designed to perform one task. Most functions take one or more parameters and return one result. Functions are the standard way to make code reusable and to break a large problem into smaller ones.
function

Function Concept

Functional programming is old: John McCarthy introduced it with Lisp in 1960. It wasn't well understood at the time and was largely ignored for decades.

Functional Languages

Functional programming is back. It fits problems such as data transformation and concurrency especially well, though it is harder to learn at first if you come from imperative languages. New languages increasingly combine object-oriented, functional, and structured programming rather than picking one.

Functional languages are built around mathematical functions that use conditional expressions and recursion to compute, rather than statements that mutate state. Functions are "first-class citizens": every sub-program is a function. Well-known functional languages include Lisp, Scheme, Haskell, Clojure, OCaml, and Racket.

Scope Model

A fundamental split between languages is their scoping model — how a variable or constant's lifetime is decided.

Dynamic scope is created at execution time. Static scope is created along with the function and stays in memory as long as the function is reachable — which lets functions behave like objects. This is exactly what makes functions "first-class citizens" with state, the key difference between structured and functional programming.

What Static Scope Enables

  • A function can be assigned to a variable, as a reference;
  • A function can receive another function as a parameter;
  • A function can produce another function as its result;
  • A function can be suspended in memory and resumed later;
  • A function can carry state (sometimes called attributes);
  • Functions can be chained with dot notation;
  • You can build a collection of function references.

Advantages

Functional programming is efficient and safer for concurrent code. Modern multi-core processors can run pure functions in parallel without the risk of one thread's mutation corrupting another's read — which is a large part of why static-scoping, functional-leaning languages have grown in popularity as core counts have grown.

Pure Languages

Pure functional languages take getting used to: no side effects means a function cannot even print to the screen without that being modeled explicitly, and pure languages typically drop classic loops and selection statements in favor of expressions and recursion. That's a real shift in how you think through a problem, even if it isn't a difficult one once it clicks.

Modern, Hybrid Languages

Most modern languages use static scoping, which keeps functions deterministic while still allowing ordinary control-flow statements inside them. Julia, Nim, Go, Swift, and Rust are recent languages built this way. Java added lambda expressions in version 8 (2014) specifically to support callback-style, functional code for generic algorithms and parallel computing; Kotlin and Scala go further, running on the JVM while implementing functional programming fully.

Representative Languages

These six languages go from the historical roots of functional programming to its most modern, type-driven forms.

LanguageWhy study it
LispThe original: lists, recursion, and code as data with macros.
SchemeA minimal Lisp with closures, tail calls and continuations; the language of SICP.
HaskellPurity, laziness and a strong type system, with effects tracked in types.
OCamlType inference, variants and modules, with mutation and objects when needed: practical functional programming.
CleanA pure lazy language close to Haskell, using uniqueness types for effects.
IdrisDependent types: types that state and prove properties of your functions.

Conclusion

Every paradigm in this roadmap — linear, structured, object-oriented, and functional — has real advantages and real trade-offs. That's exactly why most modern languages implement several of them at once rather than picking a single pure style. Understanding each paradigm on its own is what lets you recognize which one a given piece of code is really using, no matter which language it's written in.

Further Reading

A comprehensive glossary: fp-glossary.