Haskell 2026-10-24
A functional programming language with parametric polymorphism and lazy evaluation. The code examples here use the Haskell 2010 Language Report specification.
Irrefutable pattern 2026-10-24
A pattern that does not force its matched value merely to establish the match. A Haskell pattern written with
~ can defer evaluation of a recursive pair until one component is demanded. This can preserve productivity in definitions involving lazy evaluation. It does not make every later use of the components terminate. Lazy feedback stream 2026-10-24
Mutually dependent streams evaluated by lazy evaluation. A useful sufficient condition for productivity is that one participant emits its first constructor independently of any reply, while its -st output depends only on the first replies. A scorer mapping each guess to a terminating response then lets each finite interaction prefix be constructed inductively. Inspecting the reply stream before emitting the first guess instead creates a circular demand.
List unfold 2026-10-24
Generate a list from a state: a step returning
Nothing terminates; Just (x,s') emits x and continues from s'. Thus unfoldr step s = case step s of Nothing -> []; Just (x,s') -> x : unfoldr step s'. A list unfold decomposes the evolving state, whereas a list fold consumes existing constructors. Productivity depends on each demanded constructor becoming available, not merely on using lazy evaluation. Partial-list interleaving obstruction 2026-10-25
No generic sequential pure Haskell operation can guarantee emission of every available input occurrence when either of two input spines may be the bottom value. To obtain an arbitrary element, it must eventually demand one input. That input can be undefined while the other is a defined singleton, so the demand blocks the promised singleton output. Choosing the other input first reverses the counterexample. Lazy evaluation cannot determine which computation will diverge; the positive two-stream fairness result needs proper lazy lists or a stronger concurrent computational model.
The right fold replaces the two list constructors by its two arguments; the left fold instead updates an accumulator while traversing the list.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr c z [] = z
foldr c z (x:xs) = c x (foldr c z xs)
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl s a [] = a
foldl s a (x:xs) = foldl s (s a x) xsThus
foldr is the natural list fold: it preserves the constructor arrangement while replacing [] by z and (:) by c. In particular, foldr (:) [] = id. More generally, the fold factorisation criterion says that the defining equations characterize the corresponding structural recursion. Association and lazy evaluation are separate: the accumulator in this definition of left fold is not automatically evaluated at each step.[ref]Haskell 2010 Language Report. Chapter 9, PreludeList: foldr and foldl
No universally correct sequential Haskell implementation has that guarantee for partial lists. Compare
undefined paired with [v], and [v] paired with undefined, where the element type is arbitrary. A generic program cannot fabricate v; it must eventually inspect an input spine to obtain it. Whichever input it first demands can be chosen to be the undefined one, blocking access to the available element in the other input.In particular, the implementation in part b can emit a defined prefix and then block when alternation reaches an undefined tail. Lazy evaluation postpones demands; it cannot recognize that a demanded computation will never finish. The result is the partial-list interleaving obstruction, not an objection to fairness under the proper-input hypothesis. Additional concurrent primitives would change the computational model.[ref]
Haskell 2010 Language Report. §3.1, errors and nontermination; §3.17, demands made by pattern matching
The following shared step consumes exactly one input element per recursive call of The helper's input consists of nonempty runs. If its first run starts with
group1:prependRun :: Eq a => a -> [[a]] -> [[a]]
prependRun x xss = (x:same):rest
where
~(same,rest) = case xss of
[] -> ([],[])
run@(y:_):runs
| x == y -> (run,runs)
| otherwise -> ([],xss)
group1 :: Eq a => [a] -> [[a]]
group1 [] = []
group1 (x:xs) = prependRun x (group1 xs)x, prepend x to that run; otherwise create a new singleton run. Structural induction proves that these are the maximal equal runs in order. The irrefutable pair binding ~(same,rest) preserves lazy evaluation: the first output group exposes its initial x without inspecting the recursively grouped tail. Thus even an infinite constant input can expose the first group and each finite prefix of that group. Finite inputs take linear time when equality tests take constant time.The list unfold operation has the following type signature and definition:The seed is the unprocessed suffix of a list. An empty seed terminates the unfold; otherwise
unfoldr :: (s -> Maybe (a,s)) -> s -> [a]
unfoldr step s = case step s of
Nothing -> []
Just (x,s') -> x : unfoldr step s'
groupUnfold :: Eq a => [a] -> [[a]]
groupUnfold = unfoldr next
where
next [] = Nothing
next (x:xs) = Just (x:ys,zs)
where (ys,zs) = span (== x) xsnext returns the first maximal run and precisely the seed for the next call. Substituting the definition of unfoldr recovers part (c)'s equations, proving correctness. The Just pair and first group constructor can be produced before span finishes, retaining the same lazy evaluation behaviour.A strict function satisfies , where is the bottom value representing an undefined or nonterminating computation. This is a semantic implication: obtaining a defined result requires enough definedness of the argument. It does not prescribe an evaluation order or say that every component of a structured argument is evaluated.[ref]
For example, in Haskell,is strict, since every result is , but never needs to inspect its argument. Moreover, lazy evaluation may leave an entire application unevaluated when its result is unused. Strictness is therefore different from the operational statement that an argument is evaluated on every call.
f :: a -> b
f _ = undefinedThe most informative occurrence-position representation of a sequence of natural numbers isEach nonnegative integer occurs once at its own index, and negative integers never occur.
naturals :: Seq Integer
naturals v
| v < 0 = []
| otherwise = [v]This is not equal to
rep [0..] under lazy evaluation. For a nonnegative query v, rep eventually emits v, but then searches forever for another occurrence: its result has the shape v : undefined, rather than the terminating singleton [v]. For a negative query, even the outer constructor is never produced, whereas naturals immediately returns [].No general, correct
rep :: Eq a => [a] -> Seq a can always certify those absences from an arbitrary infinite input. A terminating certification has inspected only finitely many elements, and the same prefix could be followed by the supposedly absent value, or by a further occurrence of it. Extra knowledge changes the problem: for a promised strictly increasing integer input, a search can stop with [] when it first exceeds the query and return [i] immediately upon equality. That specialised representation can produce naturals; a generic search cannot. This is the absence-detection obstruction for infinite lists.Fold into a membership predicate:At an empty tree the predicate is always false. At a nonempty binary search tree, an unequal query can occur only in the indicated child, while equality finds the root. This proves correctness inductively. Under lazy evaluation, the unused folded child is not demanded; lookup follows one root-to-leaf route and takes comparisons in a tree of height . Defining the predicate by a fold does not require visiting both subtrees for each query.
find :: Ord a => Tree a -> a -> Bool
find = foldTree node (const False)
where
node left x right q = case compare q x of
LT -> left q
EQ -> True
GT -> right qA tree fold can compute insertion while retaining the unchanged children by carrying a pair. The following fold-derived implementation preserves the lazy evaluation behaviour of part (f):The invariant of the folded pair is
insertViaFold :: Ord a => a -> Tree a -> Tree a
insertViaFold q t = snd (foldTree node base t)
where
base = (Empty, Tree Empty q Empty)
node ~(l,li) x ~(r,ri) = (original,updated)
where
original = Tree l x r
updated = case compare q x of
LT -> Tree li x r
EQ -> original
GT -> Tree l x ri(original subtree, subtree with q inserted). The empty case establishes it. At a node, the first components reconstruct the original tree, and the second components supply whichever child insertion is needed. The irrefutable pair patterns ~(l,li) and ~(r,ri) avoid demanding an unused child's fold. Induction on finite approximations therefore proves equivalence even for partial trees, and only the selected search route needs insertion work.There is a distinction if “written as a fold” is required to mean a direct same-result-type expression Here
insert q = foldTree node base, with no richer result and final projection. On finite fully defined binary search trees, such an expression is possible by undoing insertion of a new extreme key in the child that should remain unchanged. For completeness, a literal direct fold on that finite, fully defined domain is:eraseFresh :: Ord a => a -> Tree a -> Tree a
eraseFresh _ Empty = Empty
eraseFresh q (Tree l x r) = case compare q x of
LT -> Tree (eraseFresh q l) x r
EQ -> Empty
GT -> Tree l x (eraseFresh q r)
insertDirectFold :: Ord a => a -> Tree a -> Tree a
insertDirectFold q = foldTree node (Tree Empty q Empty)
where
node left x right = case compare q x of
LT -> Tree left x (eraseFresh q right)
EQ -> Tree (eraseFresh q left) x (eraseFresh q right)
GT -> Tree (eraseFresh q left) x righteraseFresh is used only to undo a newly inserted extreme key, which is a leaf; it is not a general deletion operation. The binary search tree bound at each root guarantees exactly that precondition on every child being restored.On all partial Haskell trees, however, a direct expression with result type
Tree a cannot implement part (f).For that obstruction, fix Both can be completed to valid right subtrees of a root labelled
q = 0, and consider the two right subtreesr1 = Tree Empty undefined Empty
r2 = Tree Empty undefined (Tree Empty 3 Empty)1. Yet insert 0 r1 and insert 0 r2 are both bottom, because insertion must compare 0 with their undefined root label. A direct fold equal to insertion would therefore receive the same folded left value, root 1, and bottom folded right value on both larger trees Tree Empty 1 r1 and Tree Empty 1 r2. It must return the same result. Part (f) instead preserves r1 or r2, which are distinguishable by inspecting their right-child constructors without inspecting the undefined labels. This proves the direct-fold obstruction. The pair-valued implementation avoids losing that information and states precisely which sense of fold implementation is being used. Stream productivity 2026-10-24
A stream computation is productive when every demanded finite prefix of its output can be produced in finite work. Filtering an infinite list need not be productive: asking for odd elements of
[0,2..] never produces even an empty-list constructor. Lazy evaluation alone does not prove productivity.