Skip to content
Historical revision
Functional Programming2026-05-16 19:17 UTC
Edit summary: AI translation (ko -> en-US)
rev_e6efcef1e7f2470eba41f91218756e87

Functional Programming

Functional Programming

Functional Programming (FP) is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. In Korean, it is commonly referred to as 함수형 프로그래밍 (functional programming) or 함수형 패러다임 (functional paradigm).

Functional Programming, alongside object-oriented programming, forms one of the two dominant paradigms in the modern software industry, yet its theoretical roots are considerably older. While object-oriented programming emerged from simulation and GUI work in the 1960s, lambda calculus, the foundation of functional programming, originated in mathematical logic in the 1930s. For this reason, functional programming is sometimes called "the old new paradigm."

TXT
객체지향 = 데이터와 그 데이터를 조작하는 행위를 객체 단위로 묶는다
함수형  = 데이터를 변환하는 순수 함수의 합성으로 프로그램을 구성한다

Core Definitions

Functional programming is a paradigm that places strong emphasis on the following properties.

TXT
1. 함수가 일급 시민(first-class)이다.
2. 함수는 수학적 의미의 함수에 가깝다 (동일 입력 → 동일 출력).
3. 상태 변경(mutation)과 부수 효과(side effect)를 회피한다.
4. 반복(loop)보다 재귀(recursion)와 고차 함수(higher-order)를 선호한다.
5. 자료는 변경되지 않고 새 자료를 만든다 (immutability).

Languages that enforce all five of these properties at 100% are rare. The degree of enforcement varies, but the functional mindset has been partially adopted in virtually every modern language.

History

The history of functional programming begins not in industry but in mathematical logic. This starting point is what makes the culture and vocabulary of functional programming distinct from that of object-oriented programming.

Lambda Calculus (1930s)

The lambda calculus, formalized by Alonzo Church between 1932 and 1936, demonstrated that function abstraction and application alone are sufficient to express any computation. The Church-Turing thesis asserts that lambda calculus and Turing machines possess equivalent computational power.

TXT
람다 계산법의 기본 연산:
- 변수: x
- 추상: λx. M    (x를 받아 M을 반환하는 함수)
- 적용: M N      (함수 M에 인자 N을 적용)

These three syntactic constructs alone are sufficient to express natural numbers, Booleans, data structures, recursion, and all computation. Every core concept in modern functional languages is a direct descendant of lambda calculus.

Lisp (1958)

Lisp (LISt Processor), introduced by John McCarthy in 1958, was the first programming language inspired by lambda calculus. McCarthy's 1960 paper "Recursive Functions of Symbolic Expressions and Their Computation by Machine" serves as its foundational reference.1

That said, care is needed when characterizing Lisp as a "functional language." Lisp introduced core elements of the functional mindset (first-class functions, a recursion-centric approach, and homoiconicity), but it freely permitted mutable variables and side effects. In other words, Lisp is the starting point of functional programming, not a purely functional language. This nuance undermines the accuracy of the common claim that Lisp is the "originator of functional programming."

ISWIM and Theoretical Foundations (1960s)

Peter Landin introduced the hypothetical language ISWIM (If you See What I Mean) in his 1966 paper "The Next 700 Programming Languages."2 Although ISWIM was never actually implemented, it exerted a profound influence on the syntax and semantics of subsequent functional languages. The let/where constructs, expression-oriented design, and the offside-rule style of thinking all trace back to this lineage.

Backus's Turing Award Lecture (1977)

In his 1977 Turing Award lecture, "Can Programming Be Liberated from the Von Neumann Style?", John Backus criticized imperative programming for being too tightly bound to Von Neumann architecture and proposed function-level programming instead.3 This lecture is frequently cited as the industrial legitimization of the functional programming movement.

An interesting footnote: Backus was the designer of FORTRAN. The fact that the very person who created the archetypal imperative language went on to identify the limitations of that paradigm gives the lecture even greater weight.

The ML Family (1973)

ML (Meta Language), created by Robin Milner around 1973 for the LCF (Logic for Computable Functions) theorem prover, is the landmark example of combining static type inference with a functional language.4 The Hindley-Milner type system became widely known through the ML family and went on to serve as the foundation for statically typed functional languages.

ML family descendants:

  • Standard ML (1983)

  • Caml (1985) / OCaml (1996)

  • F# (2005) - Microsoft, OCaml-based

  • Influenced: the type systems of Rust, Swift, and Scala

Miranda and Lazy Evaluation (1985)

Miranda, released by David Turner in 1985, was a commercially supported pure functional language combining lazy evaluation, pure functions, and algebraic data types.5 Trademark issues made Miranda difficult to use as a shared research language in academia, and the need for an open standard to unify the various lazy functional languages became one of the driving motivations behind the creation of Haskell.

Haskell (1990)

Haskell began in 1987 as a committee effort to establish a common standard for lazy-evaluation-based functional languages, and published its first standard (Haskell 1.0) in 1990.6 The name comes from Haskell Curry, a researcher in lambda calculus.

Haskell's defining contributions:

  • Adopted lazy evaluation as the default

  • Preserved purity by separating I/O through the IO monad

  • Introduced type classes (Wadler & Blott, 1989)

  • Became the standard reference point for functional thinking in both academia and industry

Personal note: there is a joke that "programming in Haskell means no bugs, but also no finished programs." I like that joke.

Concurrency-first functional programming (Erlang, 1986)

Erlang, created in 1986 by Joe Armstrong and colleagues at Ericsson, is a functional language designed for concurrency and reliability in telecommunications systems. Through the actor model, lightweight processes, and the "let it crash" philosophy, it became one of the de facto standards in distributed systems. Elixir (2011) is its successor, layering a modern syntax on top of the Erlang VM.

The resurgence of modern functional programming (2000s onward)

Factors that brought functional programming back into the spotlight in industry:

  • The arrival of the multicore era, as the concurrency cost of mutable state grew significantly

  • The emergence of JVM/CLR functional languages such as Scala (2003), Clojure (2007), and F# (2005)

  • Rust (2010 onward), a systems language with strong functional influences

  • React + Redux (2015 onward), introducing unidirectional data flow and immutability to the JavaScript frontend

  • TypeScript (2012 onward), with discriminated unions and type-level expressiveness

Functional Programming is no longer a "specialized paradigm" but has been partially absorbed into every modern language.

Core Concepts

First-class Function

This means functions are treated as equal to any other value.

TXT
- 변수에 함수를 할당할 수 있다
- 함수를 다른 함수의 인자로 넘길 수 있다
- 함수를 다른 함수의 반환값으로 받을 수 있다
- 자료구조에 함수를 저장할 수 있다

First-class functions are a prerequisite for higher-order functions to have meaning.

Pure Function

A function that satisfies both of the following conditions:

TXT
1. 동일한 입력에 대해 항상 동일한 출력을 반환한다.
2. 외부 상태에 부수 효과(side effect)를 일으키지 않는다.

Examples of side effects: mutating a global variable, writing to the console, writing to a file, network communication, and raising exceptions.

The value of a pure function stems from referential transparency: the property that any expression can be replaced by its resulting value without changing the meaning of the program. This is the foundation of functional reasoning and optimization.

Immutability

The property that once a value is created, it cannot be changed. When a modification is needed, a new value is created instead.

Haskell
-- Haskell: adding an element to a list creates a new list
xs = [1, 2, 3]
ys = 0 : xs    -- ys = [0, 1, 2, 3], xs remains unchanged

The cost of immutability is memory allocation, but persistent data structures and structural sharing bring it to a practical level of efficiency.

Higher-order Function

A function that takes other functions as arguments or returns a function as its result. It is the unit of abstraction in functional programming.

Haskell
-- map, filter, and fold are the prime examples
map    :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
foldr  :: (a -> b -> b) -> b -> [a] -> b

Just as polymorphism and inheritance serve as abstraction tools in object-oriented programming, higher-order functions fill that role in functional programming.

Function Composition

An operation that chains two functions together into a single function.

Haskell
-- Haskell: the `.` operator
double :: Int -> Int
double x = x * 2

negate :: Int -> Int
negate x = -x

doubleNegate :: Int -> Int
doubleNegate = negate . double  -- first `double`, then `negate`

Function composition is the unit of modularity in functional programming. If object-oriented programming assembles objects, functional programming composes functions.

Currying and Partial Application

A transformation that expresses a function taking multiple arguments as a sequence of functions each taking one argument. The name derives from Haskell Curry.

Haskell
-- In Haskell, every function is automatically in curried form
add :: Int -> Int -> Int
add x y = x + y

addOne :: Int -> Int
addOne = add 1    -- Partial Application

Some languages make currying automatic (Haskell, OCaml), while others require it to be done manually (JavaScript, Python).

Lazy Evaluation

An evaluation strategy that defers computation until the value is actually needed. Haskell is the canonical example.

Haskell
-- Infinite lists can also be defined
naturals = [1..]
take 5 naturals  -- [1, 2, 3, 4, 5]

Lazy evaluation offers real benefits in composability and abstraction, but its drawbacks are equally clear: space leaks and difficulty debugging. The fact that lazy evaluation is not unconditionally good is covered in the functional anti-patterns section.

Recursion and Tail Call Optimization

Functional languages use recursion instead of loops. Because naive recursion consumes stack space, tail call optimization (TCO) is important: the compiler transforms a recursive call in tail position into an iterative loop.

Haskell
-- Non-tail-recursive sum (stack accumulation)
sumBad :: [Int] -> Int
sumBad [] = 0
sumBad (x:xs) = x + sumBad xs

-- Tail-recursive sum (using an accumulator)
sumGood :: Int -> [Int] -> Int
sumGood acc [] = acc
sumGood acc (x:xs) = sumGood (acc + x) xs

Languages like Scheme and Erlang, which treat tail calls as a core part of their language and runtime design, provide stable TCO. OCaml also offers strong practical support for tail recursion. Haskell, however, is a special case: due to lazy evaluation, saying "tail recursion always keeps the stack and heap safe" is incorrect. When an accumulator builds up as deferred thunks, the result can be a space leak where the heap, rather than the stack, overflows, and in such cases strictness controls like seq, bang patterns, and foldl' become necessary.7

Functional Concepts Combined with the Type System

Algebraic Data Types (ADT)

Data is expressed as a combination of product types (AND) and sum types (OR).

Haskell
-- Product type (similar to struct/record)
data Point = Point Double Double

-- Sum type (tagged union)
data Shape = Circle Point Double
           | Rectangle Point Point
           | Triangle Point Point Point

The Result type, Option type, trees, and lists are all expressed as algebraic data types.

Pattern Matching

A construct that branches by destructuring algebraic data types.

Haskell
area :: Shape -> Double
area (Circle _ r) = pi * r * r
area (Rectangle (Point x1 y1) (Point x2 y2)) =
    abs (x2 - x1) * abs (y2 - y1)
area (Triangle ...) = ...

The core value is the exhaustiveness check, where the compiler verifies that all cases are handled. If object-oriented polymorphism is runtime dispatch, then functional pattern matching is closer to compile-time dispatch.

Type Class

An ad-hoc polymorphism mechanism introduced by Haskell. Philip Wadler and Stephen Blott proposed it in their 1989 paper How to Make Ad-Hoc Polymorphism Less Ad Hoc.8

Haskell
class Eq a where
    (==) :: a -> a -> Bool

instance Eq Int where
    x == y = primIntEq x y

Rust's traits, Scala's implicit/given, and Swift's protocols all occupy the same problem space of "abstracting a set of operations over a type." Rust traits and Scala type class patterns are frequently compared to Haskell type classes in particular. That said, the solutions each language adopts are not entirely equivalent, because their module systems, object systems, and compiler constraints differ.

Parametric Polymorphism

A function that takes a type as a parameter. While similar to C++ templates and Java generics, parametric polymorphism is more deeply integrated in functional languages.

Haskell
identity :: a -> a
identity x = x

length :: [a] -> Int

A type variable a can be instantiated to any type.

Handling Side Effects

The most challenging area in functional programming. Pure functions alone cannot express I/O, state, or exceptions.

Monad

An abstraction that encodes side effects in the type system to preserve purity. Eugenio Moggi introduced it as a semantics of computation in his 1991 paper Notions of Computation and Monads, and Philip Wadler made it practical in Haskell during the 1990s.910

Haskell
-- IO Monad: side effects made explicit in the type
main :: IO ()
main = do
    name <- getLine        -- IO String
    putStrLn ("Hello, " ++ name)

That getLine returns IO String means it is "an IO action that produces a string," not "a string itself." This distinction in types is what separates pure functions from side effects.

A common misconception to correct:

  • Monad = a form borrowed from category theory, but using it does not require knowledge of category theory

  • A monad is simply a programming tool, like if/then/else, not a mystical concept.

Personal note: the most dangerous moment when encountering monads for the first time is when you say, "Ah, I finally get it." That's usually when you stop understanding them again.

Effect System

A more general approach that expresses side effects themselves as types, rather than using monads. Algebraic effects are an active area of recent research, with languages such as Eff, Koka, and Unison exploring this direction.

TXT
모나드: 한 가지 효과를 하나의 타입으로 (IO, State, Reader 등)
효과 시스템: 여러 효과를 자유롭게 조합 가능

Functional Programming vs. Object-Oriented Programming

The two paradigms decompose the same problem along different axes.

TXT
            객체지향                  함수형
상태       객체 내부에 캡슐화       불변, 함수 외부로 명시
다형성      서브타이핑 + 다형성      매개변수 + 타입 클래스
재사용      상속, 합성, 위임         함수 합성, 고차 함수
시간       객체의 상태 변화         값의 변환 흐름
오류       예외 + try/catch         Result/Either, 모나드
모듈       클래스, 패키지           모듈, 함수, 타입

Same problem, different decomposition:

TXT
"Track the order status"

객체지향: Order 객체가 status 필드를 가지고 메서드로 상태를 바꾼다
함수형: Order는 불변. 상태 전이는 새 Order를 만드는 함수 (Order -> Order)

The two approaches are compatible. Most modern languages support both paradigms together (Scala, Rust, C#, TypeScript). The difference is simply which paradigm you adopt as your default way of thinking; it is not an either/or choice.

The Purity Spectrum

Not all functional languages enforce the same level of purity.

TXT
[순수성 강함]
Haskell, Idris, Agda, Elm
  - 부수 효과를 타입으로 분리
  - Haskell은 게으른 평가가 기본
  - Elm, Idris, Agda는 순수성/타입 discipline이 강하지만 평가 전략은 Haskell과 다르다

ML 계열 (Standard ML, OCaml, F#)
  - 가변 참조 허용하지만 명시적
  - 엄격한 평가가 기본

Lisp 계열 (Clojure, Common Lisp, Scheme, Racket)
  - Clojure는 불변 자료구조 기본
  - 다른 Lisp는 자유로운 가변성
  - 동적 타입

JVM/네이티브 다중 패러다임 (Scala, Rust, Swift)
  - 함수형 도구를 강하게 지원
  - OOP와 함께 사용

함수형 영향이 있는 명령형 (C#, JavaScript/TypeScript, Python, Java)
  - 람다, 고차 함수, 컴프리헨션 등 흡수
  - 핵심은 여전히 OOP/명령형
[순수성 약함]

Whether a given language is "functional or not" is not a binary question but a matter of where it sits on a spectrum.

Representative Techniques

map / filter / fold

The three fundamental operations for list transformation.

Haskell
map    (*2) [1,2,3,4]        -- [2,4,6,8]
filter (>2) [1,2,3,4]        -- [3,4]
foldr  (+) 0 [1,2,3,4]       -- 10

Operations that would require a for loop, an accumulator, and conditionals in imperative style can be compressed into a single-line expression.

Function Composition and Pipelines

Haskell
-- Haskell: Right to Left
processData :: [RawData] -> [Result]
processData = map normalize . filter isValid . map parse
F#
// F#: left to right (|> pipe operator)
let processData rawData =
    rawData
    |> List.map parse
    |> List.filter isValid
    |> List.map normalize

Point-Free Style

Defined purely through function composition, without explicitly naming the arguments.

Haskell
-- General Style
sumOfSquares xs = sum (map (^2) xs)

-- point-free
sumOfSquares = sum . map (^2)

It should be used only where it genuinely improves readability; taken too far, it becomes an anti-pattern.

Persistent Data Structures

A data structure that maintains immutability while avoiding full copies on partial updates. Through structural sharing, modifications can be performed in O(log n) or amortized O(1) time.

TXT
대표 사례:
- Hash Array Mapped Trie (HAMT)
- Finger Tree
- Persistent Red-Black Tree
- Clojure의 PersistentVector / PersistentHashMap
- Haskell의 Data.Map (균형 이진 트리)

Chris Okasaki's 1998 book Purely Functional Data Structures is the standard reference in this field.11

How Immutability Is Actually Implemented

Immutability does not mean "using no memory." In fact, many functional implementations allocate new nodes on the heap while sharing existing nodes. The key point is that existing values are never overwritten in place.

TXT
명령형 업데이트:
배열/객체의 같은 위치를 덮어쓴다.

함수형 업데이트:
바뀐 경로의 노드만 새로 만들고,
나머지 하위 구조는 기존 노드를 공유한다.

Structural Sharing in Lists

A Haskell-style list is conceptually a chain of cons cells.

Haskell
xs = [1, 2, 3]
ys = 0 : xs

From a memory perspective, it looks roughly like this.

TXT
xs -> [1] -> [2] -> [3] -> []

ys -> [0] -> [1] -> [2] -> [3] -> []
             ^
             xs와 tail 공유

When constructing ys, the entire [1, 2, 3] is not copied. Only a new cons cell [0] is created, and its tail shares the existing list that xs points to. This is why prepending an element with (:) is cheap. Conversely, appending to the end of a list is typically expensive because the existing prefix must be reconstructed.

Structural Sharing in Trees and Maps

Balanced-tree-based maps and sets work similarly. Changing a single key creates only the nodes along the path from the root to that key; all untouched subtrees are shared.

TXT
기존 트리:
        A
      /   \
     B     C
    / \
   D   E

E만 바꾼 새 트리:
        A'
      /    \
     B'     C   <- 공유
    /  \
   D    E'<- D also shared

This is the essence of persistent data structures. The previous version remains alive, and so does the new version. It is not "copy everything" but rather "path copying plus structural sharing."

Stack vs. Heap Perspective

Function calls still create stack frames. Local variables, return addresses, and temporary computation state can live on the stack. However, values whose lifetimes may extend beyond a call's scope — list nodes, tree nodes, closures, and lazy-evaluation thunks — typically live on the heap.

TXT
스택:
- 함수 호출 프레임
- 짧은 수명의 지역 바인딩
- 평가 중인 임시 상태

힙:
- 리스트/트리/레코드 같은 자료구조 노드
- 클로저가 캡처한 환경
- Haskell의 지연 계산 thunk
- 공유되는 persistent data structure 노드

In other words, immutability in functional programming does not mean "fast because it only uses the stack." On the contrary, it actively relies on heap allocation and garbage collection, while structural sharing and compiler optimizations keep the cost down.

Personal note: immutability is not a free lunch. The idea is that you are billed not for "copying everything" but only for "the changed path." Personally, I have nothing but a growing tab.

Haskell's Thunks and Space Leaks

Because of lazy evaluation, Haskell may defer computing a value and instead store a thunk on the heap.

Haskell
sumBad = foldl (+) 0 [1..1000000]

The code above intuitively looks like it keeps adding to an accumulator, but under lazy evaluation, a pile of unevaluated computation chunks can build up like this.

TXT
((((0 + 1) + 2) + 3) + ...)

When these chunks accumulate on the heap, the symptom is not a stack overflow but a rapid explosion in heap usage. This is why Haskell performance problems often come down not to "stack or heap" but to "which thunks are forced to evaluate, and when."

Haskell
import Data.List (foldl')

sumGood = foldl' (+) 0 [1..1000000]

foldl' evaluates the accumulator strictly at each step, reducing thunk accumulation.

Immutability in Practical Languages

In mainstream languages like JavaScript/TypeScript, C#, and Java, "immutable update" does not typically mean the runtime automatically provides persistent data structures. In practice, it usually means either shallow-copying objects or arrays, or using libraries such as Immutable.js, Immer, or Clojure-style persistent collections.

TypeScript
// Shallow-copy-based immutable updates
const next = {
  ...prev,
  status: "paid",
};

This code is an immutable update in the sense that it does not mutate prev, but that does not automatically make the entire deeply nested structure a persistent data structure. Incorrectly sharing nested objects can still cause internal mutation problems.

TXT
불변성 구현 방식:
- Haskell/Clojure: 언어/표준 자료구조 차원에서 구조 공유가 기본
- Scala/F#/OCaml: 불변 컬렉션과 가변 컬렉션을 선택
- JS/TS/React: 관례와 라이브러리로 불변 업데이트를 유지
- Rust: 소유권/빌림 규칙으로 aliasing과 mutation을 통제

Functional Programming Examples by Language

Haskell

Haskell
-- Algebraic data types + pattern matching + recursion
data Tree a = Leaf | Node a (Tree a) (Tree a)

insert :: Ord a => a -> Tree a -> Tree a
insert x Leaf = Node x Leaf Leaf
insert x (Node y left right)
    | x < y     = Node y (insert x left) right
    | x > y     = Node y left (insert x right)
    | otherwise = Node y left right

OCaml

OCaml
(* 옵션 타입과 패턴 매칭 *)
let rec find_first pred = function
    | [] -> None
    | x :: rest when pred x -> Some x
    | _ :: rest -> find_first pred rest

F#

F#
// Pipeline-Oriented
let processOrders orders =
    orders
    |> List.filter (fun o -> o.Status = Active)
    |> List.map (fun o -> { o with Total = computeTotal o })
    |> List.sortByDescending (fun o -> o.Total)

Clojure

Clojure
;; Data Transformation Pipeline
(->> orders
     (filter active?)
     (map compute-total)
     (sort-by :total >))

Scala

Scala
// for-comprehension (monad abstraction)
val result = for {
  user    <- findUser(id)
  account <- findAccount(user)
  balance <- getBalance(account)
} yield balance

JavaScript / TypeScript

TypeScript
// Higher-Order Functions and Composition
const processOrders = (orders: Order[]): Result[] =>
    orders
        .filter(o => o.status === "active")
        .map(o => ({ ...o, total: computeTotal(o) }))
        .sort((a, b) => b.total - a.total);

C#

C#
// LINQ is essentially a functional query language
var result = orders
    .Where(o => o.Status == OrderStatus.Active)
    .Select(o => o with { Total = ComputeTotal(o) })
    .OrderByDescending(o => o.Total)
    .ToList();

Rust

Rust
// Iterator chaining + closures
let result: Vec<_> = orders
    .iter()
    .filter(|o| o.status == Status::Active)
    .map(|o| Order { total: compute_total(o), ..o.clone() })
    .collect();

Concurrency and Distribution

The primary reason functional programming has experienced a resurgence in the multicore era is that immutability reduces the cost of concurrency.

TXT
가변 상태의 동시성 비용:
- 락(lock) 관리
- 경쟁 조건(race condition)
- 데드락
- 메모리 일관성 모델

불변 상태의 동시성 이득:
- 락이 필요 없다 (읽기 전용)
- 부수 효과가 명시되어 추론 가능
- 액터 모델, 메시지 패싱과 자연스럽게 결합

Notable examples:

  • Erlang / Elixir: well known for the actor model, lightweight processes, and high-availability telecommunications systems

  • Akka (Scala/Java): an actor-based framework for distributed systems

  • Hadoop / Spark: MapReduce is inherently a functional programming paradigm

  • React + Redux: unidirectional data flow, immutable state, changes through reducer functions

  • Elm: enforces pure functional programming on the frontend

Common Misconceptions and Corrections

Misconception 1: "Functional programming has no mutability whatsoever"

Inaccurate. Even purely functional languages have mechanisms for handling side effects (Haskell's IO monad, ST monad). Multi-paradigm functional languages (OCaml, F#, Scala) explicitly allow mutable references. The essence of functional programming is not "no mutation" but rather "immutable by default, mutation made explicit."

Misconception 2: "Functional programming doesn't use objects"

Inaccurate. F#, OCaml, and Scala all have object systems. Haskell also builds object-like abstractions through records and type classes. Functional programming is not a negation of OOP but a paradigm with different priorities.

Misconception 3: "Recursion is always better than iteration"

Inaccurate. In languages that do not guarantee tail call optimization (TCO) (JavaScript, Python), deep recursion causes stack overflows. When clarity is equal, it is better to follow the form the language supports naturally.

Misconception 4: "You need to know category theory to understand monads"

Inaccurate. The intuition of a monad as a box with a consistent interface is sufficient. Category theory is the mathematical background of monads, not a prerequisite for using them.

Personal note: that said, say this out loud in a functional programming community and brace for seizures. Every programming community eventually optimizes for memes and turns dogmatic.

Misconception 5: "Functional programming is not practical in industry"

Inaccurate. Functional languages and functional styles are used in real industrial systems, as shown by WhatsApp's Erlang/FreeBSD infrastructure, Jane Street's OCaml, Meta/Facebook's Haskell-based Sigma, Twitter's Scala, and Klarna's Erlang.1213141516 It is more accurate to say that industry adoption is not absent, but tends to concentrate in specific domains such as high-availability telecommunications, finance, rules engines, and distributed services.

Personal note: I labeled this a misconception, but honestly I'm not sure it actually is one. I learned functional programming because people said it would bump up the leading digit of your salary, and it did change things. Just in the direction of a decrease.

Functional Anti-Patterns

1. Side effects inside map

TypeScript
// Incorrect usage: the purpose of map is to produce a result list
items.map(item => sendEmail(item));   // The result list is discarded

// Correct usage: use forEach for side effects
items.forEach(item => sendEmail(item));

2. Excessive point-free style

Haskell
-- Code that tells you nothing about what it does
processAll = ((.) . (.)) (flip (.)) (uncurry . curry)

Point-free style should only be used when it improves readability.

Personal note: Point-free code at this level is closer to submitting a cipher than transferring knowledge to your colleagues.

3. Premature monad abstraction

Haskell
-- Stacking monad transformer layers for simple tasks
type App a = ReaderT Config (StateT World (ExceptT Error IO)) a

A three-level stacked monad is almost always overkill. Start with a single IO or a single Either, then raise the level of abstraction only when necessary.

4. Space leaks caused by lazy evaluation

The classic Haskell pitfall.

Haskell
-- `foldl` builds a large accumulating thunk due to lazy evaluation
sumBad = foldl (+) 0 [1..1000000]

-- `foldl'` evaluates the accumulator eagerly
sumGood = foldl' (+) 0 [1..1000000]

If you don't know the difference between foldl and foldl', memory usage will explode on large lists. This is the trap that lazy evaluation sets.

5. Using type class hierarchies like OOP class hierarchies

Haskell
-- Anti-pattern: mimicking deep inheritance hierarchies with type classes
class Animal a where ...
class Animal a => Mammal a where ...
class Mammal a => Dog a where ...

Type classes are not a substitute for OOP inheritance hierarchies. They are a different tool entirely.

6. Accumulating intermediate lists

Haskell
-- An intermediate list is created at every step
result = sum . map (*2) . filter even . map (+1) $ list

List fusion often optimizes this automatically, but it is safer to explicitly consolidate using streams or folds.

Criticisms of Functional Programming

Learning Curve

Mathematical vocabulary (monad, functor, applicative) and abstract thinking raise the barrier to entry. Developers accustomed to imperative or object-oriented styles need to go through a process of "re-learning."

Complexity of the I/O Model

Composing effects through monad transformers is powerful but carries a steep learning cost. Research into effect systems (algebraic effects) represents an ongoing effort to simplify this.

Memory Allocation Overhead

Immutability causes frequent allocation of new objects. Persistent data structures and compiler optimizations help mitigate this, but it remains a burden in latency-critical systems.

The Readability Debate

Point-free style, monadic do-notation, and type classes are powerful once familiar, but look cryptic to newcomers. A practical criticism is that code review costs can be high.

Why It Matters Now

The renewed attention functional programming has received since the 2010s is not simply a passing trend.

TXT
- 멀티코어 시대: 가변 상태 동시성 비용이 커짐
- 분산 시스템: 불변성과 메시지 패싱이 자연스러움
- 데이터 파이프라인: map/reduce는 본질적으로 함수형
- 프론트엔드 상태 관리: React/Redux의 단방향 흐름
- 타입 시스템 발전: TypeScript, Rust가 ML 계열 영향을 흡수

John Hughes's Why Functional Programming Matters is a classic paper that frames the core value of functional programming as the modularity afforded by higher-order functions and lazy evaluation.17 Paul Hudak's survey is a representative overview that traces the history and technical characteristics of functional languages from lambda calculus through Lisp, ISWIM, FP, ML, Miranda, and Haskell.18

Today, "becoming a functional programmer" means less about working exclusively in Haskell and more about knowing how to apply functional thinking regardless of which language you use.

Learning Path

Getting started with functional thinking is more about shifting your mindset than choosing a language.19

TXT
1. 익숙한 언어에서 시작
   - JavaScript의 map/filter/reduce
   - Python의 list comprehension, functools
   - C#의 LINQ
   - 가변성을 줄이고 함수 합성을 의식적으로 사용

2. 다중 패러다임 함수형 언어로 확장
   - F#, OCaml, Scala 중 하나
   - 타입 시스템과 패턴 매칭에 익숙해지기

3. 순수 함수형 도전
   - Haskell 또는 Elm
   - 모나드, 타입 클래스, 게으른 평가 학습

4. 이론적 기반 보강
   - 람다 계산법
   - 타입 이론 입문 (Pierce "Types and Programming Languages") 
   - 카테고리 이론 (선택사항)

Key questions:

TXT
이 문제를 데이터 변환의 흐름으로 표현할 수 있는가?
이 상태 변경을 새 값 생성으로 바꿀 수 있는가?
이 부수 효과를 타입으로 명시할 수 있는가?
이 반복을 고차 함수로 표현할 수 있는가?

Summary

Functional Programming is a paradigm rooted in lambda calculus from the 1930s, evolving through Lisp (1958), ML (1973), and Haskell (1990) into the modern era. Its five core principles are as follows.

TXT
1. 함수는 일급 시민이다
2. 함수는 수학적 함수에 가깝다 (순수성)
3. 자료는 불변이다
4. 부수 효과는 명시된다
5. 추상화 단위는 고차 함수와 함수 합성이다

Functional Programming is not a replacement for object-oriented programming but a complement to it. It is regaining attention in the age of multi-core and distributed systems, and has been partially absorbed into most modern languages. A "functional programmer" is less a user of a specific language and more a developer who knows how to apply functional thinking.

See Also

  • Programming Paradigms

  • Object-Oriented Programming

  • Design Patterns

  • Programming Idioms

  • Lambda Calculus

  • Monad

  • Type Systems

  • Algebraic Data Type

  • Result Type

  • Option Type

  • Persistent Data Structures

  • Referential Transparency

  • Higher-Order Functions

  • Function Composition

  • Lazy Evaluation

  • Tail Call Optimization

  • Hindley-Milner Type System

  • Actor Model

References

Official Documentation / Language Sites

Key Papers / Historical Sources

Overview


Category: Programming | Programming Paradigms | Programming Languages | Software Engineering | WIKI

Footnotes

  1. McCarthy, John (1960). "Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I". Communications of the ACM. 3 (4): 184–195.
  2. Landin, Peter J. (1966). "The Next 700 Programming Languages". Communications of the ACM. 9 (3): 157–166.
  3. Backus, John (1978). "Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs". Communications of the ACM. 21 (8): 613–641. doi:10.1145/359576.359579.
  4. Milner, Robin; Tofte, Mads; Harper, Robert; MacQueen, David (1997). *The Definition of Standard ML (Revised)*. MIT Press. ML's historical background connects to the LCF theorem prover.
  5. Turner, David. The Miranda Programming Language. Miranda is a lazy/pure functional language with commercial support from Research Software Ltd. and directly influenced Haskell.
  6. Hudak, Paul; Hughes, John; Peyton Jones, Simon; Wadler, Philip (2007). "A History of Haskell: Being Lazy With Class". HOPL III.
  7. GHC User's Guide. "Bang patterns and Strict Haskell". GHC notes that lazy evaluation is the default, and thunk elimination and strictness control are critical in performance-sensitive code.
  8. Wadler, Philip; Blott, Stephen (1989). "How to Make Ad-Hoc Polymorphism Less Ad Hoc". POPL '89: Proceedings of the 16th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages. pp. 60–76. doi:10.1145/75277.75283.
  9. Wadler, Philip (1995). "Monads for functional programming". Advanced Functional Programming. Lecture Notes in Computer Science. Vol. 925. pp. 24–52. doi:10.1007/3-540-59451-5_2. Moggi's theoretical starting point should also be consulted in "Notions of Computation and Monads".
  10. Moggi, Eugenio (1991). "Notions of computation and monads90052-4)". Information and Computation. 93 (1): 55–92. doi:10.1016/0890-5401(91)90052-4.
  11. Okasaki, Chris (1998). *Purely Functional Data Structures*. Cambridge University Press. ISBN 0-521-66350-4.
  12. Reed, Rick. "That's 'Billion' with a 'B': Scaling to the Next Level at WhatsApp". InfoQ / Erlang Factory SF Bay, 2014. WhatsApp's scaling case study based on Erlang and FreeBSD.
  13. Jane Street. "Technology at Jane Street". Jane Street writes as many systems as possible in OCaml.
  14. Meta Engineering. "Fighting spam with Haskell". Facebook/Meta's application of Haskell in the Sigma rule engine.
  15. Twitter. "Effective Scala". Twitter's internal guide to Scala usage and functional/expression-oriented style.
  16. Erlang Solutions. "Klarna case study". Klarna's high-availability payment system built on Erlang.
  17. Hughes, John (1989). "Why Functional Programming Matters". The Computer Journal. 32 (2): 98–107. doi:10.1093/comjnl/32.2.98. Author's public version: Chalmers mirror.
  18. Hudak, Paul (1989). "Conception, Evolution, and Application of Functional Programming Languages". ACM Computing Surveys. 21 (3): 359–411. doi:10.1145/72551.72554.
  19. Pierce, Benjamin C. (2002). *Types and Programming Languages*. MIT Press. ISBN 978-0-262-16209-8.