For finite lists, use structural induction: establish
P []; then, for arbitrary x and xs, assume P xs and prove P (x:xs). The induction hypothesis must quantify over any other parameters that need to change in the step.For all Haskell list values, finite terminating spines are insufficient: also establish
P undefined, and allow arbitrary, possibly undefined, heads in the cons step. This proves the equation for finite partial lists ending in the bottom value. An infinite lazy list is the limit of these finite approximations. Equations between pure definable computable functions are preserved by those limits, so the equation extends to infinite inputs. This last argument requires that admissibility property; an arbitrary logical property of lists need not have it. The distinction between [] and the bottom value is essential.[ref]Prove the stronger equation, quantified over the accumulator:For Thus structural induction proves the claim for finite and finite partial spines; definable continuity extends it to infinite lazy lists. Pointwise equality is exactly the asserted equality of function compositions. This is the left-fold fusion law.
[], both sides are f a. For the bottom value, both sides are bottom because f is a strict function. For the cons step, the universal induction hypothesis allows the accumulator to become g a x:f (foldl g a (x:xs))
= f (foldl g (g a x) xs)
= foldl h (f (g a x)) xs
= foldl h (h (f a) x) xs
= foldl h (f a) (x:xs).Repeated root removal gives the conventional list:The representation interpretation satisfies
toList :: List a -> [a]
toList rs
| null rs = []
| otherwise = head rs : toList (tail rs)toList (cons x rs) = x : toList rs; alternatively, structural induction on the number of stored elements proves that each step emits exactly the next preorder traversal value. The output has cons cells, and each step takes constant time, so fully consuming it costs time. No repeated list concatenation is needed.Skip whole trees using their cached sizes, then descend through one tree:After skipping a size- tree, the desired index decreases by . Inside a node, position zero is the root, positions belong to the left child and the following positions to the right child. This proves correctness by structural induction on the search.
index :: List a -> Int -> a
index (List ts) i
| i < 0 = error "index: negative index"
| otherwise = seek ts i
where
seek [] _ = error "index: out of bounds"
seek ((w,t):us) j
| j < w = at w t j
| otherwise = seek us (j-w)
at _ (Leaf x) 0 = x
at w (Node x l r) j
| j == 0 = x
| j <= h = at h l (j-1)
| otherwise = at h r (j-1-h)
where h = w `div` 2
at _ _ _ = error "index: invalid representation"There are at most forest entries by the supplied bound, and each tree has logarithmic height. Consequently a valid query takes time, with logarithmic worst cases. On a conventional linked list,
xs !! i follows links and costs , hence in the worst case. The first element remains a constant time query in either representation.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.Share the recursive pair and inspect the predicate once:For a finite list, structural induction establishes that
partition :: (a -> Bool) -> [a] -> ([a],[a])
partition _ [] = ([],[])
partition p (x:xs)
| p x = (x:yes,no)
| otherwise = (yes,x:no)
where ~(yes,no) = partition p xsyes and no are the two required filtered subsequences. The guard evaluates p x once and prepends x to exactly one output, preserving input order in both. Fully consuming both results uses predicate applications and additional work. Sharing the recursive result avoids making separate recursive traversals. This is a stable list partition.Join either two halves or successive pairs:By the defining property of
unhalve :: ([a],[a]) -> [a]
unhalve (xs,ys) = xs ++ ys
unpairs :: [(a,a)] -> [a]
unpairs = foldr (\(x,y) zs -> x:y:zs) []splitAt, concatenating the two halves restores the original list. For unpairs, structural induction on the even input proves that its first pair restores its first two elements and the recursive result restores the rest. Thus both requested function compositions are the identity. Fully consuming either result takes at most linear time; list concatenation only copies the left half once.Apply structural induction to the formation of a propositional formula. Let and count its opening and closing parentheses, and use the usual grammar generated by propositional variables, any allowed truth constants, negation, and parenthesized binary logical connectives.
An atomic variable or truth constant contains neither kind of parenthesis, so . If , prefixing negation leaves both counts unchanged. If the notation writes a negated formula with an extra pair of parentheses, it adds one to each and still preserves equality.
For a binary construction , where is an allowed binary logical connective,The two induction hypotheses make these equal. Every grammar construction preserves the property, so every propositional formula contains equally many left and right parentheses. This is balanced parentheses in propositional syntax.
The list fold criterion is: there must be a value
z and a definable combining function c such thatThe last equality says that all information needed about the tail factors through f xs; the combining function receives no other access to xs. These conditions give f = foldr c z.Necessity follows from the defining equations of
foldr, including its case distinction on the input list. Sufficiency follows by structural induction for finite lists and finite partial-list approximations. For infinite or partial lazy lists, the usual continuous semantics of these definable functions extends equality from the approximations to their limit. The strictness condition matters: agreement only on finite total lists does not establish agreement at . This is the fold factorisation criterion.The direct tree fold for an inorder traversal isBy structural induction, the child lists are ascending; the binary search tree property places every left value below the root and every right value above it. The displayed list concatenation therefore gives an ascending list containing every stored value exactly once.
flattenFold :: Tree a -> [a]
flattenFold = foldTree (\ls x rs -> ls ++ (x:rs)) []For a version with linear time complexity on every tree shape, use an accumulator:This also has a higher-order tree fold expression using difference lists:Its folded value is a function that prepends the inorder traversal to a supplied tail. The accumulator invariant is
flatten :: Tree a -> [a]
flatten t = onto t []
where
onto Empty zs = zs
onto (Tree l x r) zs = onto l (x : onto r zs)flattenLinear :: Tree a -> [a]
flattenLinear t =
foldTree (\l x r -> l . (x:) . r) id t []onto t zs = flattenFold t ++ zs. Each node contributes one constructor, giving time to consume the output. By contrast, the direct list-valued fold can take on a left-skewed tree because ++ repeatedly copies left traversals.Use the remaining ascending list itself as the seed:The empty seed produces
buildUnfold :: [a] -> Tree a
buildUnfold = unfoldTree split
where
split [] = Nothing
split xs =
let (ls,x:rs) = splitAt (length xs `div` 2) xs
in Just (ls,x,rs)Empty. Every nonempty seed produces exactly the midpoint and two child seeds used by build. Thus the defining equations agree, and structural induction on the finite input proves buildUnfold xs = build xs. The balance and ordering arguments carry over unchanged.Use a customer object with no selected platforms as the base component, and wrap it once for each selected channel. Each decorator object has the same interface, delegates existing work, adds one platform send, and adds that platform's per-message charge. The code below uses the
Customer and Comms interfaces from the question; every decorator receives the communication implementation explicitly.final class NoChannels(val name: String) extends Customer {
def spread(msg: String): Unit = ()
def cost: Double = 0.0
}
abstract class CustomerDecorator(protected val inner: Customer)
extends Customer {
final val name: String = inner.name
def spread(msg: String): Unit = inner.spread(msg)
def cost: Double = inner.cost
}
final class OnDiscord(c: Customer, out: Comms)
extends CustomerDecorator(c) {
override def spread(msg: String): Unit = {
super.spread(msg)
out.sendD(name, msg)
}
override def cost: Double = super.cost + 0.01
}
final class OnFacebook(c: Customer, out: Comms)
extends CustomerDecorator(c) {
override def spread(msg: String): Unit = {
super.spread(msg)
out.sendF(name, msg)
}
override def cost: Double = super.cost + 0.05
}
final class OnWhatsApp(c: Customer, out: Comms)
extends CustomerDecorator(c) {
override def spread(msg: String): Unit = {
super.spread(msg)
out.sendW(name, msg)
}
override def cost: Double = super.cost + 0.03
}
def customer(name: String, discord: Boolean, facebook: Boolean,
whatsapp: Boolean, out: Comms): Customer = {
var c: Customer = new NoChannels(name)
if (discord) c = new OnDiscord(c, out)
if (facebook) c = new OnFacebook(c, out)
if (whatsapp) c = new OnWhatsApp(c, out)
c
}The factory admits each platform at most once, matching a subset of preferences. For example,
customer("reader", true, false, true, out) sends once to Discord and once to WhatsApp and has cost per message, using the stipulated charges. The innermost wrapper executes first, giving deterministic Discord–Facebook–WhatsApp order for selected channels. The zero-platform case sends nothing and costs zero. Repeated messages reuse the same immutable choice of wrappers.By structural induction on the wrapper chain, the base sends to the empty subset with zero cost; one additional wrapper preserves all prior sends and adds exactly its channel and charge. Thus the sends and charge are composed from the same selected components. There are channel-specific classes instead of one class per subset, and a new channel needs one new wrapper. A direct manual construction containing the same wrapper twice would intentionally send and charge twice, so the factory prevents that configuration.
cost computes a per-message amount rather than charging by itself; a later payment mechanism can use it. The interface uses Double, so numerical tests should allow ordinary floating-point rounding. Perfect rose tree 2026-10-31
A rose tree is perfect when all its leaves of a rooted tree have the same depth. This permits unequal branching counts and unary branches; perfectness alone does not require every internal vertex to have the same number of children. A bottom-up certificate returns
Just 0 at a leaf, Just (h+1) when all child certificates are Just h, and Nothing otherwise. Structural induction proves that a certificate exists exactly when the tree is perfect: all root-to-leaf routes acquire one edge when attached to the new root. Predicate prefix-suffix reconstruction 2026-10-24
Concatenating the initial list prefix accepted by a predicate with the remaining list suffix reconstructs the input, provided every test reached before the first false result terminates. Prove the finite case by structural induction: a true test copies the head, and a false test returns the untouched list suffix. An undefined reached test is an obstruction because reconstruction loses an exposed cons cell. An infinite all-true list prefix can still reconstruct the input, since list concatenation never demands its second argument there.
In the grammar , no logical formula is a proper initial segment of another. A recursive parser reads one variable, or a negation followed by one formula, or a parenthesized pair of formulas separated by implication. Structural induction shows that this parser consumes precisely one whole formula. Its deterministic stopping position proves prefix-freeness. Consequently any formula substring in a valid formula coincides with the formula of a syntax subtree.
Right-fold fusion law 2026-10-24
If is strict, , and , then . The empty case uses the base image and the cons case uses compatibility followed by structural induction. Strictness handles a bottom-valued spine. For pure definable continuous functions, equality on finite partial lists extends to infinite lists by finite approximation. For finite total inputs alone, induction does not require the strictness hypothesis.
Suppose a first-order language has logical implication as its only connective and has either or both of the quantifiers, but no primitive falsity. In a singleton structure with every atomic predicate true, all atomic equalities are also true, and structural induction makes every formula true. No formula of this language can therefore express a negated equality. Quantifiers cannot repair this failure of expressive completeness, even though they add syntactic operations beyond propositional connectives.
Structural induction on lazy lists 2026-10-31
For an admissible predicate on lazy lists , prove and , and prove for every possibly partial head . Constructor induction then establishes on every finite partial approximation, and admissibility extends it to limits, including infinite lists. Without the bottom value case and the limit condition, ordinary finite structural induction does not justify a claim about infinite data.[ref]
https://ethz.ch/content/dam/ethz/special-interest/infk/inst-infsec/information-security-group-dam/people/andreloc/lochbihler14itps.pdf. Recursive Functions on Lazy Lists, ITP 2014 presentation, slide 7: admissibility restriction on induction over lazy lists
Structural recursion 2026-10-13
Recursion following the constructors of a finite inductive datatype. Correctness is naturally proved by structural induction.
// Target: computer-science.bigb
Structure isomorphism 2026-10-31
A structure isomorphism is a bijective structure embedding: it preserves constants and functions, and both preserves and reflects relations. Its inverse has the same properties. It therefore preserves truth of every first-order formula, by structural induction, so it preserves realization and omission of parameter-free types.