✍️ 필사 모드: The Practical Value of Functional Programming — Monad, Functor, Pure Functions, Haskell, Erlang/Elixir, Gleam, Effect-TS, Phoenix LiveView (2025)
EnglishTL;DR — Functional programming (FP) is no longer a mathematician's hobby; it's the mindset of the concurrency and distribution era. React Hooks is functional state, Redux is
reduce, RxJS is the Observable Monad, Swift/Rust/Kotlin'sOption/Resultare Maybe/Either Monads, and Erlang/Elixir achieves 99.9999999% (nine nines) availability via pure functions plus the actor model. In 2024-2025, a new generation emerged: Effect-TS (TypeScript), Gleam (typed language on BEAM), Elixir + Phoenix LiveView, Roc (Elm's successor), and Unison (distributed pure). This piece traces why "side-effect management" — FP's core — matters more in the cloud era, how to understand Monads without category theory, and what modern languages borrowed from FP.
Why Functional, Now
FP began with LISP (1958), flowered through Haskell and ML in the 1980s-90s, but industry called it "the academics' language." That changed mid-2010s:
- 2015: React declared immutable props + pure render function
- 2016: Redux —
state = reducer(state, action), effectivelyreduce - 2017: React Hooks proposed (shipped 2019) — functions over classes
- 2019: Rust 1.0 adopts ownership + Result/Option wholesale
- 2020: Swift 5.5 —
async/await + Task + Result - 2021: Kotlin
Result<T>, JavaOptional<T>mainstream - 2023: TypeScript Effect-TS 1.0 — "the Haskell of TypeScript"
- 2024: Gleam 1.0 — typed FP on BEAM (Erlang VM)
- 2024: Zed Editor, Fly.io Phoenix LiveView — Elixir revival
- 2025: WhatsApp (2B users), Discord, Pinterest, Bleacher Report expand Erlang/Elixir
Three reasons why now:
- Concurrency — tens of thousands of concurrent requests + shared-state lock hell → immutability and message passing as the answer
- Reliability — partial failure in distributed systems → "let it crash" vindicated
- Type safety — algebraic data types + pattern matching demonstrably cut bugs
Pure Functions — the starting point
A pure function: same input, same output, no side effects.
// pure
function add(a, b) { return a + b }
function double(arr) { return arr.map(x => x * 2) }
// impure
let counter = 0
function incCounter() { counter++; return counter }
function logAndAdd(a, b) {
console.log(a, b)
return a + b
}
function fetchUser(id) {
return fetch(`/api/users/${id}`)
}
Practical Value
- Testable — input alone reproduces output; no mocks
- Parallelizable — no shared state, thread-safe by default
- Memoizable — caching by input (React.memo, useMemo)
- Reasonable — readers trust there's no hidden global mutation
- Composable — predictable combination
Referential transparency: replacing a call with its result preserves program meaning. Another name for purity.
Side Effects Never Vanish
Real programs need I/O, DB, and network. FP's insight: don't eliminate side effects — push them to the edges and encode them in types.
- Haskell:
IO a - Rust:
Result<T, E> - Effect-TS:
Effect<R, E, A>— requirement, error, value - Elm: Cmd/Sub — runtime handles effects
Immutability — Forbidding Mutation by Design
// mutable
const arr = [1, 2, 3]
arr.push(4)
const obj = { a: 1 }
obj.a = 2
// immutable
const arr2 = [1, 2, 3]
const arr3 = [...arr2, 4]
const obj2 = { a: 1 }
const obj3 = { ...obj2, a: 2 }
Why Immutability Matters
- Change tracking — React's
prev === nextcomparison in O(1) - Concurrency-safe — multiple readers, no problem
- Time-travel debugging — why Redux DevTools works
- Easy undo/redo — keep past states
- Easy memoization — same reference = same value
Persistent Data Structures — the performance fix
Naive immutability copies everything (O(n)). Persistent data structures use structural sharing for O(log n).
- Immutable.js — List, Map, Set
- Immer — write to a draft, get an immutable copy
- Clojure — built-in Hash Array Mapped Trie
- Scala — persistent Vector, HashMap
import { produce } from 'immer'
const state = { users: [{ id: 1, name: 'Alice' }] }
const nextState = produce(state, draft => {
draft.users.push({ id: 2, name: 'Bob' })
draft.users[0].name = 'Alice Updated'
})
Higher-Order Functions — Functions as Values
[1, 2, 3].map(x => x * 2)
[1, 2, 3].filter(x => x > 1)
[1, 2, 3].reduce((acc, x) => acc + x, 0)
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x)
const toString = x => x.toString()
const exclaim = s => s + '!'
const shout = s => s.toUpperCase()
const greet = compose(shout, exclaim, toString)
greet(42) // '42!'
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) return fn(...args)
return (...more) => curried(...args, ...more)
}
}
const add = curry((a, b, c) => a + b + c)
add(1)(2)(3)
Pipelines — Left to Right
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x)
const result = pipe(
filter(u => u.active),
map(u => u.email),
xs => xs.join(', ')
)(users)
Elixir has the pipe operator natively:
users
|> Enum.filter(& &1.active)
|> Enum.map(& &1.email)
|> Enum.join(", ")
Option / Maybe — A World Without null
Tony Hoare called null the "billion-dollar mistake." FP languages encode absence in the type system.
fn find_user(id: u64) -> Option<User> {
if id == 0 { None } else { Some(User { id, name: "Alice".into() }) }
}
find_user(42)
.map(|u| u.name)
.unwrap_or("anonymous".into())
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
const findUser = (id: number): O.Option<User> =>
id === 0 ? O.none : O.some({ id, name: 'Alice' })
const result = pipe(
findUser(42),
O.map(u => u.name),
O.getOrElse(() => 'anonymous')
)
findUser :: Int -> Maybe User
findUser 0 = Nothing
findUser id = Just User { userId = id, userName = "Alice" }
Result / Either — Typed Failure
Exceptions hide control flow from signatures. Result/Either encodes success/failure in types.
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 { Err("division by zero".into()) }
else { Ok(a / b) }
}
let result = divide(10.0, 2.0)?;
import { Effect } from 'effect'
const divide = (a: number, b: number): Effect.Effect<never, string, number> =>
b === 0
? Effect.fail("division by zero")
: Effect.succeed(a / b)
const program = Effect.gen(function* () {
const x = yield* divide(10, 2)
const y = yield* divide(x, 0)
return x + y
})
Monad — What Even Is It
A Monad is FP's notorious concept. It comes from category theory, but practically it's just a chainable wrapping type.
Definition (practical)
A Monad provides two operations:
of(x)— wrapxinto the Monad (unit,pure,return)flatMap(f)— applyfto the inner value;freturns a Monad (bind)
type Maybe<T> = { tag: 'some', value: T } | { tag: 'none' }
const of = <T>(x: T): Maybe<T> => ({ tag: 'some', value: x })
const flatMap = <A, B>(m: Maybe<A>, f: (a: A) => Maybe<B>): Maybe<B> =>
m.tag === 'some' ? f(m.value) : m
const parseInt = (s: string): Maybe<number> => {
const n = Number(s)
return isNaN(n) ? { tag: 'none' } : { tag: 'some', value: n }
}
const addTen = (n: number): Maybe<number> => of(n + 10)
const result = flatMap(parseInt("42"), addTen)
Why Monads
Monads let diverse side effects share one interface:
- Maybe/Option — absence
- Either/Result — failure
- Promise/Future/IO — async/I-O
- List — nondeterminism
- State — state mutation
- Reader — environment
- Writer — logging
Monad Laws
- Left identity:
of(x).flatMap(f) === f(x) - Right identity:
m.flatMap(of) === m - Associativity:
m.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))
do-notation
Haskell:
main = do
line <- getLine
let n = read line :: Int
print (n * 2)
TypeScript (Effect-TS):
const program = Effect.gen(function* () {
const user = yield* fetchUser(1)
const posts = yield* fetchPosts(user.id)
return { user, posts }
})
JavaScript async/await is essentially the Promise Monad's do-notation.
Functor, Applicative, Monad — Three Layers
The FP type-class hierarchy:
- Functor — only
map. "Apply a function inside a container." - Applicative — Functor +
of+ap. "Combine multiple wrapped values." Used for collecting validation errors. The combinators<$>and<*>in Haskell are Applicative. - Monad — Applicative +
flatMap. "Sequence that depends on the previous result."
Haskell — Lessons From "the Mathematicians' Language"
Haskell (1990) is pure, lazy, statically typed. Niche in production, but its influence on modern languages is enormous.
Haskell's Innovations
- Type class — ad-hoc polymorphism (root of Rust traits, Scala implicits, Swift protocols)
- Algebraic Data Types — sum + product (Rust enum, Swift enum, TS discriminated union)
- Pattern matching — universal now
- Monad — encode I/O as pure functions
- Lazy evaluation — infinite list
[1..]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs -- [0,1,1,2,3,5,8,13,21,34]
class Eq a where
(==) :: a -> a -> Bool
data Shape = Circle Double | Rectangle Double Double | Triangle Double Double Double
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle a b c) = sqrt (s * (s-a) * (s-b) * (s-c))
where s = (a + b + c) / 2
Haskell in Production
- Facebook Sigma — spam filtering (10B requests/day)
- GitHub Semantic — code analysis
- Standard Chartered — financial modeling
- Target — price optimization
- Mercury — banking (since 2020)
Erlang/Elixir — Nine-Nines Availability
Erlang was built by Ericsson in 1986 for telephone switches. Elixir (José Valim, 2012) layers Ruby-like syntax atop the Erlang VM (BEAM).
BEAM's Four Superpowers
- Lightweight processes — 300-400 bytes each; millions concurrent
- Preemptive scheduler — multicore-aware; per-process GC
- Message passing — no shared memory; actor model
- Hot code swap — replace code at runtime without downtime
"Let It Crash"
defmodule Worker do
use GenServer
def handle_call(:dangerous_op, _from, state) do
result = dangerous_call()
{:reply, result, state}
end
end
defmodule MyApp.Supervisor do
use Supervisor
def init(_) do
children = [
{Worker, []},
]
Supervisor.init(children, strategy: :one_for_one, max_restarts: 3, max_seconds: 60)
end
end
Principle: don't write defensive code — let it fail, let the Supervisor restart. This is the heart of OTP.
Phoenix + LiveView — Realtime UI Without React
defmodule MyAppWeb.CounterLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
def handle_event("inc", _value, socket) do
{:noreply, assign(socket, count: socket.assigns.count + 1)}
end
def render(assigns) do
~H"""
<div>
<p>Count: <%= @count %></p>
<button phx-click="inc">+1</button>
</div>
"""
end
end
The server pushes DOM diffs over WebSocket. SPA-class UX without a JS framework. Fly.io, Supabase Realtime, Discord run this at scale.
Real-World Usage
- WhatsApp — 2 engineers, 2M concurrent users (2012); now 2B users
- Discord — 5M concurrent per Elixir server
- Pinterest — notifications on Erlang
- Goldman Sachs, Klarna, Bleacher Report
Gleam — 2024's 1.0 Typed Language on BEAM
Where Elixir is dynamic, Gleam (Louis Pilfold, since 2016) is statically typed on BEAM. 1.0 shipped March 2024.
import gleam/io
pub type Shape {
Circle(radius: Float)
Square(side: Float)
}
pub fn area(shape: Shape) -> Float {
case shape {
Circle(r) -> 3.14159 *. r *. r
Square(s) -> s *. s
}
}
pub fn main() {
let c = Circle(radius: 5.0)
io.debug(area(c))
}
- Rust-level type inference
- Bidirectional interop with Elixir/Erlang
- Also compiles to JavaScript
- Rust-ish syntax
Clojure, F#, Scala, OCaml — Hybrid FP
Clojure
- LISP dialect on the JVM (Rich Hickey, 2007)
- Immutability default, persistent data structures
- Macros, core.async (CSP)
- Walmart, Nubank, NASA
(defn add [a b] (+ a b))
(->> [1 2 3 4 5]
(filter odd?)
(map #(* % %))
(reduce +))
F#
- Microsoft (Don Syme, 2005), OCaml-based on .NET
- Jet.com, Walmart Labs, finance
Scala
- OOP + FP hybrid on JVM (Martin Odersky, 2003)
- Twitter, LinkedIn, Spotify; Akka, Spark
OCaml
- INRIA, 1996
- Jane Street, Facebook (Flow, Hack), early Docker, Coq
React, Redux, RxJS — JS's FP Inheritance
React's core idea: UI = f(state).
function Greeting({ name }) {
return <h1>Hello, {name}</h1>
}
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Redux is literally reduce:
function reducer(state, action) {
switch (action.type) {
case 'INC': return { ...state, count: state.count + 1 }
case 'DEC': return { ...state, count: state.count - 1 }
default: return state
}
}
RxJS is the Observable Monad — streams of values over time:
const click$ = fromEvent(button, 'click').pipe(
map(e => e.clientX),
filter(x => x > 100),
debounceTime(300)
)
Effect-TS — TypeScript's Haskell
1.0 in 2024. A TypeScript port of Scala ZIO.
import { Effect, pipe, Layer } from 'effect'
interface Logger {
log: (msg: string) => Effect.Effect<never, never, void>
}
const Logger = Context.Tag<Logger>()
const program = Effect.gen(function* () {
const logger = yield* Logger
yield* logger.log("starting...")
const user = yield* fetchUser(1)
yield* logger.log(`got user ${user.name}`)
return user
})
const LoggerLive = Layer.succeed(Logger, {
log: (msg) => Effect.sync(() => console.log(msg))
})
Effect.runPromise(pipe(program, Effect.provide(LoggerLive)))
- Unifies DI + error handling + async + retry + fibers, type-safe
- Avoids Monad Transformer hell at large scale
- Steep learning curve, big productivity payoff for complex systems
Concurrency Models
- Actor (Erlang/Elixir/Akka) — processes + mailboxes; no shared memory; millions concurrent
- CSP (Go, Clojure core.async) — channels; "share memory by communicating"
- STM — memory as DB transactions (Haskell
STM, Clojureref) - Promise/Future (JS, Rust, Python, Swift) — futures + async/await
2024-2025 Languages to Watch
- Roc (Richard Feldman, Elm successor) — 0.1 in 2024, LLVM-based, Platform abstraction
- Unison — "distributed pure programming"; functions are hashes, transferred by reference
- Koka (Daan Leijen, Microsoft) — Algebraic Effects, a Monad alternative
- Lean 4 — math proofs + general programming (Terence Tao uses it)
10 Common Anti-Patterns
- Monad hell — wrapping everything destroys readability. Use Monads at the edges.
- Pure purity — dodging all I/O is impractical. Concentrate effects at the boundary.
reduceoveruse — complex reducers are unreadable. Split into map/filter.- Lazy-eval abuse — hard to debug, leaks memory.
- Forced curry — breaks IDE type hints in JS/TS.
- Immutability obsession — local mutable vars are fine; enforce immutability at edges.
- Too-long chains — 10-step
.map.filter.reduceis often clearer imperative. - Monad Transformer stacks — 4-5 layers deep? Consider Effect-TS/Koka.
- Actor overuse — making everything an Actor is over-engineering. CPU-heavy work wants data-oriented design.
- Dynamic FP — Monad chains without types are hell. Start in a typed language.
Learning Checklist (2025)
- Fluent map/filter/reduce in any language
- Option/Result in Rust, Swift, Kotlin
- Pattern matching + ADTs (TS discriminated union, Rust enum)
- Pick an immutable library (Immer, Immutable.js, Clojure, Scala)
- Understand Promise chain as Monad
- Core RxJS operators — map, filter, mergeMap, switchMap
- React Hooks as memoization (useMemo, useCallback)
- One Actor or CSP pattern — Elixir GenServer or Go channel
- Experiment with Effect-TS or fp-ts
- Two-week Haskell exploration — expand your mental model
- Elixir + Phoenix LiveView — realtime UI
- Monad laws — intuition, not memorization
Next Post — "Why Is Kubernetes So Complex?"
If FP raises the reliability of individual code, Kubernetes and Cloud Native govern the reliability of the whole system. Google open-sourced its Borg experience in 2014 and K8s became the OS of the cloud — with infamous complexity to match.
Next post covers: problems K8s solved and created; Pod/Service/Deployment/Ingress basics; control-plane architecture (etcd, API Server, Scheduler, Controller); networking (CNI, kube-proxy, Service Mesh — Istio, Linkerd, Cilium); sidecar vs Ambient Mesh; GitOps (ArgoCD, Flux); Helm vs Kustomize vs Jsonnet; Platform Engineering (Backstage, IDP); Operator pattern + CRDs; multi-cluster (Karmada, Cluster API, KubeFed); cost (OpenCost, Kubecost, Karpenter); 2025 trends (WASM, eBPF, Gateway API). Starting from the fundamental question — "is Kubernetes really needed?" — through full operational recipes.
현재 단락 (1/338)
FP began with **LISP** (1958), flowered through Haskell and ML in the 1980s-90s, but industry called...