Diagonal merging of lazy lists 2026-10-24
For an outer proper lazy list of inner proper lazy lists, introduce row in round and advance each active row once per round. Discard an active row when its terminating constructor is observed. Each round inspects finitely many rows, and the entry at row index , within-row index , is emitted in round . Every earlier round has finite work, proving fair enumeration of all occurrences.
An implementation can keep a finite list of row remainders, append at most one newly introduced row, collect one head from every nonempty remainder, and retain their tails for the next round. It must not first combine all not-yet-introduced rows. In fact,
foldr interleave [] ([1,2] : repeat []) can emit 1 and then block before 2; diagonal merging of lazy lists emits both. If infinitely many empty rows remain after the final output, detecting termination is impossible in general: the absence-detection obstruction for infinite lists prevents a universal promise of a terminating empty tail, while every required occurrence can still be emitted. Fair enumeration 2026-10-24
A fair enumeration emits every required occurrence after finitely many computation steps. The inputs can be infinite; completeness does not mean waiting until they have all been exhausted. If two proper lazy lists alternate their next available entries, every occurrence reaches the output after finitely many swaps. In contrast, a nested list comprehension can remain forever in its first infinite inner list. Fairness concerns input occurrences, even when different occurrences happen to have equal values.
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.
split :: Int -> [a] -> ([a],[a])
split n xs | n <= 0 = ([],xs)
split _ [] = ([],[])
split n (x:xs) = (x:ys,zs)
where (ys,zs) = split (n-1) xsFor a defined count and a spine with available constructors before termination, each of
take and drop makes recursive calls, whereas split makes that many once. The number of recursive calls is halved. The irrefutable pattern in the local pair binding also avoids evaluating the recursive pair merely to expose the first output element.On proper lazy lists this has the intended two component values. There is a small bottom value distinction outside that domain: the displayed single-pass definition must inspect a positive-count input spine before returning its outer pair, whereas the literal expression
(take n xs, drop n xs) exposes the pair constructor even if xs is undefined. The algorithmic answer concerns the two components and the stated call count; it need not silently claim equality under a test that observes this outer constructor alone.For proper lazy lists, the generic condition is a finite first list, or an empty second list. A finite first list reaches its terminating constructor, after which list concatenation exposes the second one. An infinite first list never yields control to the second; it therefore cannot deliver occurrences originating there unless there are none. This concerns input occurrences, rather than an accidental overlap of the two inputs' value sets.
Swap roles after emitting one element:
interleave :: [a] -> [a] -> [a]
interleave [] ys = ys
interleave (x:xs) ys = x : interleave ys xsWhile both lists are nonempty, the function alternates between them. Their -th input occurrences appear after at most output steps, counting positions from zero; once either list ends, the other is returned directly. Every output constructor comes from one input constructor, and every input occurrence is eventually selected. This proves fair enumeration and productivity on the two proper lazy lists, including two infinite ones.
Merge the rows with
fairConcat, retaining explicit empty-input cases:fairPair :: (a -> b -> c) -> [a] -> [b] -> [c]
fairPair _ [] _ = []
fairPair _ _ [] = []
fairPair f xs ys = fairConcat [[f x y | y <- ys] | x <- xs]For nonempty proper lazy lists, the occurrence
f (xs!!i) (ys!!j) belongs to row , position , so the proof in part d puts it in round . This supplies every pair after finite work, even when both input lists are infinite. The guards also return [] for either empty factor without traversing infinitely many empty rows.cp :: [[a]] -> [[a]]
cp [] = [[]]
cp (xs:xss) = fairPair (:) xs (cp xss)The empty family has one selection, the empty selection. For a nonempty finite family, each selection consists uniquely of one first-factor occurrence followed by a selection from the remaining factors. Induction on the number of factors, together with the fairness of
fairPair, proves that every such selection is produced and no invalid selection is introduced. Infinite factors are allowed: in particular, the three-factor example is enumerated fairly. If any factor is empty, the finite-family computation returns the empty product.The printed hypothesis also permits an infinite outer proper lazy list. No function can satisfy the full enumeration requirement for all such inputs. Take
repeat [0,1]. Its mathematical Cartesian product contains every infinite binary sequence and is uncountable. If a result list enumerated these sequences as , the binary sequence with would differ from the -th listed sequence at position , contradicting completeness. This is an uncountability obstruction to enumerating infinite Cartesian products. The finite-outer qualification is a necessary repair to the specification, not a restriction imposed for convenience by the code. Proper lazy list 2026-10-24
A proper lazy list has a spine exposing either finitely many cons constructors followed by
[], or infinitely many cons constructors, without an undefined tail at a finite position. This is a condition on the spine: an element may itself be the bottom value without hiding the list's constructors. A partial list with a finite visible prefix followed by fails this condition. The distinction supports productivity proofs that inspect constructors without evaluating the stored elements.