075 — Difference List
Tutorial Video
Text description (accessibility)
This video demonstrates the "075 — Difference List" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. A difference list represents a list as a function from "remaining suffix" to "complete list". Key difference from OCaml: 1. **`Box<dyn Fn>` vs plain function**: Rust requires `Box<dyn Fn>` because each closure has a unique type. OCaml's closures have a uniform representation — `'a list
Tutorial
The Problem
A difference list represents a list as a function from "remaining suffix" to "complete list". Appending two difference lists is O(1) — just function composition — instead of O(n). This is important in functional programming where repeated left-fold appends create O(n²) behavior.
Difference lists originate in Prolog (1984) and appear in Haskell's ShowS type for efficient string building. The key insight: [1,2,3] as a difference list is fun suffix -> [1,2,3] ++ suffix. Appending is fun suffix -> dl1(dl2(suffix)). ShowS = String -> String in Haskell is exactly a difference list for strings.
🎯 Learning Outcomes
DList::empty(), singleton(x), from_vec(v), append(self, other), to_vec()(dl1.append(dl2)).run(tail) = dl1.run(dl2.run(tail))ShowS (Haskell) and Buffer (OCaml) as practical applicationsCode Example
#![allow(clippy::all)]
/// # Difference List — O(1) Append
///
/// A difference list represents a list as a function from "rest of list" to "full list".
/// This makes append O(1) — just function composition — instead of O(n).
///
/// In Rust, this pattern is less common because Vec already has O(1) amortized push_back.
/// But it's a great exercise in higher-order functions and closures.
/// A difference list is a function: given a tail, prepend our elements.
/// We use Box<dyn Fn> since closures have unique types.
pub struct DList<T> {
f: Box<dyn Fn(Vec<T>) -> Vec<T>>,
}
impl<T: 'static + Clone> DList<T> {
/// Empty difference list — identity function.
pub fn empty() -> Self {
DList {
f: Box::new(|rest| rest),
}
}
/// Singleton — prepends one element.
pub fn singleton(x: T) -> Self {
DList {
f: Box::new(move |mut rest| {
rest.insert(0, x.clone());
rest
}),
}
}
/// From a Vec.
pub fn from_vec(v: Vec<T>) -> Self {
DList {
f: Box::new(move |rest| {
let mut result = v.clone();
result.extend(rest);
result
}),
}
}
/// O(1) append — just compose the two functions.
pub fn append(self, other: DList<T>) -> DList<T> {
DList {
f: Box::new(move |rest| (self.f)((other.f)(rest))),
}
}
/// Convert to Vec — apply the function to an empty list.
pub fn to_vec(&self) -> Vec<T> {
(self.f)(vec![])
}
}
/// Alternative: just use Vec! Rust's Vec already has O(1) amortized push.
/// This shows that the difference list pattern, while elegant in OCaml/Haskell,
/// is often unnecessary in Rust.
pub fn concat_with_vec(lists: &[Vec<i32>]) -> Vec<i32> {
let total_len: usize = lists.iter().map(|l| l.len()).sum();
let mut result = Vec::with_capacity(total_len);
for list in lists {
result.extend(list);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() {
let dl: DList<i32> = DList::empty();
assert_eq!(dl.to_vec(), vec![]);
}
#[test]
fn test_singleton() {
let dl = DList::singleton(42);
assert_eq!(dl.to_vec(), vec![42]);
}
#[test]
fn test_from_vec() {
let dl = DList::from_vec(vec![1, 2, 3]);
assert_eq!(dl.to_vec(), vec![1, 2, 3]);
}
#[test]
fn test_append() {
let a = DList::from_vec(vec![1, 2, 3]);
let b = DList::from_vec(vec![4, 5, 6]);
let c = DList::singleton(7);
let result = a.append(b).append(c);
assert_eq!(result.to_vec(), vec![1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn test_multiple_appends() {
let dl = DList::from_vec(vec![1])
.append(DList::from_vec(vec![2]))
.append(DList::from_vec(vec![3]))
.append(DList::from_vec(vec![4]));
assert_eq!(dl.to_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn test_concat_with_vec() {
let lists = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
assert_eq!(concat_with_vec(&lists), vec![1, 2, 3, 4, 5, 6]);
}
}Key Differences
Box<dyn Fn> vs plain function**: Rust requires Box<dyn Fn> because each closure has a unique type. OCaml's closures have a uniform representation — 'a list -> 'a list is a first-class type without boxing.FnOnce vs Fn**: Rust's DList here uses FnOnce — each difference list can only be used once (because append consumes both). A version using Fn requires T: Clone. OCaml's closures can be called multiple times.String::push_str and Vec::extend are already amortized O(1), so difference lists are less critical than in OCaml with its immutable lists. The concept is pedagogically important.ShowS in Haskell**: Haskell's type ShowS = String -> String is a difference list for strings. showsPrec uses it to build Show instances efficiently.OCaml Approach
OCaml's difference list is an elegant application of higher-order functions:
type 'a dlist = 'a list -> 'a list
let empty : 'a dlist = fun xs -> xs
let singleton x = fun xs -> x :: xs
let append dl1 dl2 = fun xs -> dl1 (dl2 xs) (* O(1): function composition *)
let to_list dl = dl []
let from_list lst = fun xs -> lst @ xs
(* append [1;2] [3;4]: build dl1 . dl2, then apply to [] *)
let result = to_list (append (from_list [1;2]) (from_list [3;4]))
(* result = [1; 2; 3; 4] *)
This is isomorphic to Haskell's ShowS = String -> String and DList a = [a] -> [a].
Full Source
#![allow(clippy::all)]
/// # Difference List — O(1) Append
///
/// A difference list represents a list as a function from "rest of list" to "full list".
/// This makes append O(1) — just function composition — instead of O(n).
///
/// In Rust, this pattern is less common because Vec already has O(1) amortized push_back.
/// But it's a great exercise in higher-order functions and closures.
/// A difference list is a function: given a tail, prepend our elements.
/// We use Box<dyn Fn> since closures have unique types.
pub struct DList<T> {
f: Box<dyn Fn(Vec<T>) -> Vec<T>>,
}
impl<T: 'static + Clone> DList<T> {
/// Empty difference list — identity function.
pub fn empty() -> Self {
DList {
f: Box::new(|rest| rest),
}
}
/// Singleton — prepends one element.
pub fn singleton(x: T) -> Self {
DList {
f: Box::new(move |mut rest| {
rest.insert(0, x.clone());
rest
}),
}
}
/// From a Vec.
pub fn from_vec(v: Vec<T>) -> Self {
DList {
f: Box::new(move |rest| {
let mut result = v.clone();
result.extend(rest);
result
}),
}
}
/// O(1) append — just compose the two functions.
pub fn append(self, other: DList<T>) -> DList<T> {
DList {
f: Box::new(move |rest| (self.f)((other.f)(rest))),
}
}
/// Convert to Vec — apply the function to an empty list.
pub fn to_vec(&self) -> Vec<T> {
(self.f)(vec![])
}
}
/// Alternative: just use Vec! Rust's Vec already has O(1) amortized push.
/// This shows that the difference list pattern, while elegant in OCaml/Haskell,
/// is often unnecessary in Rust.
pub fn concat_with_vec(lists: &[Vec<i32>]) -> Vec<i32> {
let total_len: usize = lists.iter().map(|l| l.len()).sum();
let mut result = Vec::with_capacity(total_len);
for list in lists {
result.extend(list);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() {
let dl: DList<i32> = DList::empty();
assert_eq!(dl.to_vec(), vec![]);
}
#[test]
fn test_singleton() {
let dl = DList::singleton(42);
assert_eq!(dl.to_vec(), vec![42]);
}
#[test]
fn test_from_vec() {
let dl = DList::from_vec(vec![1, 2, 3]);
assert_eq!(dl.to_vec(), vec![1, 2, 3]);
}
#[test]
fn test_append() {
let a = DList::from_vec(vec![1, 2, 3]);
let b = DList::from_vec(vec![4, 5, 6]);
let c = DList::singleton(7);
let result = a.append(b).append(c);
assert_eq!(result.to_vec(), vec![1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn test_multiple_appends() {
let dl = DList::from_vec(vec![1])
.append(DList::from_vec(vec![2]))
.append(DList::from_vec(vec![3]))
.append(DList::from_vec(vec![4]));
assert_eq!(dl.to_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn test_concat_with_vec() {
let lists = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
assert_eq!(concat_with_vec(&lists), vec![1, 2, 3, 4, 5, 6]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() {
let dl: DList<i32> = DList::empty();
assert_eq!(dl.to_vec(), vec![]);
}
#[test]
fn test_singleton() {
let dl = DList::singleton(42);
assert_eq!(dl.to_vec(), vec![42]);
}
#[test]
fn test_from_vec() {
let dl = DList::from_vec(vec![1, 2, 3]);
assert_eq!(dl.to_vec(), vec![1, 2, 3]);
}
#[test]
fn test_append() {
let a = DList::from_vec(vec![1, 2, 3]);
let b = DList::from_vec(vec![4, 5, 6]);
let c = DList::singleton(7);
let result = a.append(b).append(c);
assert_eq!(result.to_vec(), vec![1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn test_multiple_appends() {
let dl = DList::from_vec(vec![1])
.append(DList::from_vec(vec![2]))
.append(DList::from_vec(vec![3]))
.append(DList::from_vec(vec![4]));
assert_eq!(dl.to_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn test_concat_with_vec() {
let lists = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
assert_eq!(concat_with_vec(&lists), vec![1, 2, 3, 4, 5, 6]);
}
}
Deep Comparison
Difference List — OCaml vs Rust Comparison
Core Insight
Difference lists solve a problem that's acute in functional languages: list append (@ / ++) is O(n) because linked lists can only be traversed from the head. By representing a list as a function rest → our_elements ++ rest, append becomes function composition — O(1). In Rust, Vec::extend() is already O(m) (where m is the appended portion), making this pattern more educational than practical.
OCaml Approach
Brilliantly minimal. A difference list is just 'a list -> 'a list — a type alias for a function. empty = Fun.id, singleton x = fun rest -> x :: rest, append a b = fun rest -> a (b rest). Pure function composition, no data structure at all. This is where OCaml's first-class functions shine.
Rust Approach
Requires Box<dyn Fn(Vec<T>) -> Vec<T>> for the stored function, since closures have unique types. Each operation allocates a new boxed closure. The T: Clone bound is needed because closures may be called multiple times. In practice, Rust programmers would just use Vec with extend() — it's simpler and faster.
Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Memory | Closures (GC'd) | Box<dyn Fn> (heap allocated) |
| Null safety | N/A | N/A |
| Errors | N/A | N/A |
| Iteration | Function application | Function application + Vec ops |
| Necessity | Solves O(n) append problem | Unnecessary — Vec already O(1) push |
Things Rust Learners Should Notice
Vec has O(1) amortized push; no need for difference listsBox<dyn Fn> overhead** — each closure allocation costs more than Vec operationsT: 'static + Clone** — required for closures to own and reproduce values|rest| f(g(rest)) is the core trick, same in both languagesFurther Reading
Exercises
type Dstr = Box<dyn FnOnce(String) -> String> and implement dstr_singleton(c: char), dstr_append, and dstr_to_string. Compare with repeated String::push for deep append trees.[1..n] using left-associative appends with both Vec and DList. Measure the time difference for n=100,000. Explain the O(n²) vs O(n) complexity.to_vec(), implement DList::into_iter(self) -> impl Iterator<Item=T> that yields elements lazily without materializing the full Vec.