Skip to content

Chapter 3 of 8

Data Structures and Types

Functional programming favors immutable data structures that, when modified, produce new versions while preserving the old. Persistent data structures achieve this efficiently through structural sharing: instead of copying the whole structure, they reuse unchanged subtrees from the original. Libraries like Immutable.js and languages like Clojure provide persistent vectors, maps, and sets that achieve roughly \( O(\log n) \) updates this way. The underlying implementation often uses a trie with a branching factor of 32, so updating element \( i \) requires copying only \( O(\log_{32} n) \) nodes along the path to \( i \) — effectively constant time for vectors up to billions of elements.

Several classical structures remain useful in functional settings. The cons list, built from a constructor that prepends an element to another list, gives \( O(1) \) prepend but \( O(n) \) random access and forms the basis of Lisp, Scheme, and Clojure. A difference list represents a list as a function from a list to a list, turning append into function composition and achieving \( O(1) \) append, which is useful in logic programming and in Haskell's Show instance. Rose trees, with a value and a list of child subtrees, model abstract syntax trees and other hierarchical data, while finger trees are general-purpose persistent sequences that support amortized \( O(1) \) cons and snoc, \( O(\log n) \) append, and \( O(\log n) \) search, parameterizable as sequences, priority queues, or ordered sets.

Algebraic data types, or ADTs, are composite types built by combining other types. Sum types (also called tagged unions or discriminated unions) represent values that are one of several variants, such as \( \text{Either}\langle A, B \rangle \), and they correspond to logical OR. Product types combine multiple values into one — tuples, structs, and records — and correspond to logical AND. Pattern matching is a destructuring mechanism that inspects a value against these patterns, going beyond simple switch/case to allow nested matching, guards, and exhaustive handling of all cases. Records in Haskell provide named field accessors and convenient update syntax, while the newtype declaration introduces a zero-cost wrapper that is distinct at the type level but identical at runtime, letting programmers add semantic meaning to existing types without performance overhead. Generalized algebraic data types, or GADTs, extend ADTs with type-equality constraints in their constructors, enabling type-indexed data structures and embedded domain-specific languages. Phantom types add type-level information that does not appear in the constructor, giving a way to encode distinctions like user tokens versus admin tokens at zero runtime cost.

All chapters
  1. 1Foundations of Functional Programming
  2. 2Functions and Higher-Order Programming
  3. 3Data Structures and Types
  4. 4Recursion, Folds, and Evaluation
  5. 5Functors, Applicatives, and Monads
  6. 6Type System Concepts
  7. 7Lambda Calculus and Category Theory
  8. 8Real-World Functional Programming and Concurrency

Drill it

Reading is not remembering. These come from the Functional Programming deck:

Q

What is functional programming?

Functional programming is a declarative programming paradigm that treats computation as the evaluation of mathematical functions. It emphasizes immutability, pu...

Q

What is a pure function?

A pure function is a function that: Always returns the same output for the same input (deterministic)Has no side effects — it doesn't modify external state, per...

Q

What are side effects in functional programming?

Side effects are any observable changes a function makes beyond returning a value. Examples include: Modifying a global or external variableWriting to a file or...

Q

What is immutability?

Immutability means that once a data structure is created, it cannot be changed. Instead of modifying data, you create new copies with the desired changes. Benef...