Length
This page is part of a series on Arithmetic Made Difficult.
This post is literate Haskell; you can load the source into GHCi and play along.
{-# LANGUAGE NoImplicitPrelude #-}
module Length
( addlength, length, _test_length, main_length
) where
import Testing
import Functions
import Flip
import NaturalNumbers
import Plus
import Lists
import LeftFold
import Snoc
import Reverse
import Cat
Today we’ll measure the sizes of lists with \(\length\). Intuitively this function should “count” the “number” of “items” in a list. Thinking recursively, it is reasonable to want the length of \(\nil\) to be zero, and the length of \(\cons(a,x)\) to be one more than the length of \(x\). \(\foldr(\ast)(\ast)\) was made for situations like this. But wait! Remember that \(\foldr(\ast)(\ast)\) is not tail recursive, so on large lists it may have problems. But \(\foldl(\ast)\) is tail recursive, and is interchangeable with \(\foldr(\ast)(\ast)\) as long as whatever we’re doing to the list doesn’t care what order the items come in. And it seems reasonable to say that the length of a list is not dependent on the order of its items. So we’ll use \(\foldl(\ast)\). Recall from \(\rev\) that \(\foldl(\ast)\) is easier to reason about if it remains parameterized on the “base case”. With that in mind, we start with a helper function \(\addlength\).
Let \(A\) be a set. We define \(\addlength : \nats \rightarrow \lists{A} \rightarrow \nats\) by \(\addlength = \foldl(\compose(\const)(\next))\).
In Haskell:
Since \(\addlength\) is defined as a \(\foldl(\ast)\), it is the unique solution to a system of functional equations.
Let \(A\) be a set. Then \(\addlength\) is the unique map \(f : \nats \times \lists{A} \rightarrow \nats\) such that for all \(n \in \nats\), \(a \in A\), and \(x \in \lists{A}\), we have \[\left\{\begin{array}{l} f(n,\nil) = n \\ f(n,\cons(a,x)) = f(\next(n),x) \end{array}\right.\]
_test_addlength_nil_right
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test (n -> Bool)
_test_addlength_nil_right t _ =
testName "addlength(n,nil) == n" $
\n ->
let nil' = nil `withTypeOf` t in
eq (addlength n nil') n
_test_addlength_cons_right
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test (n -> a -> t a -> Bool)
_test_addlength_cons_right _ _ =
testName "addlength(n,cons(a,x)) == addlength(next(n),x)" $
\n a x ->
eq (addlength n (cons a x)) (addlength (next n) x)
\(\addlength\) interacts with \(\cons\) and \(\snoc\).
Let \(A\) be a set. For all \(n \in \nats\), \(a \in A\), and \(x \in \lists{A}\), we have the following.
- \(\addlength(n,\snoc(a,x)) = \next(\addlength(n,x))\).
- \(\addlength(n,\cons(a,x)) = \next(\addlength(n,x))\).
- We have \[\begin{eqnarray*} & & \addlength(n,\snoc(a,x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-addlength} = & \foldl(\compose(\const)(\next))(n,\snoc(a,x)) \\ & \href{/posts/arithmetic-made-difficult/Snoc.html#thm-snoc-foldl} = & \compose(\const)(\next)(\foldl(\compose(\const)(\next))(n,x),a) \\ & \href{/posts/arithmetic-made-difficult/Functions.html#def-compose} = & \const(\next(\foldl(\compose(\const)(\next))(n,x)))(a) \\ & \href{/posts/arithmetic-made-difficult/Functions.html#def-const} = & \next(\foldl(\compose(\const)(\next))(n,x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-addlength} = & \next(\addlength(n,x)) \end{eqnarray*}\] as claimed.
- We proceed by list induction on \(x\). For the base case \(x = \nil\), we have \[\begin{eqnarray*} & & \addlength(n,\cons(a,\nil)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-addlength-cons} = & \addlength(\next(n),\nil) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-addlength-nil} = & \next(n) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-addlength-nil} = & \next(\addlength(n,\nil)) \end{eqnarray*}\] as needed. For the inductive step, suppose the equality holds for all \(a\) and \(n\) for some \(x\), and let \(b \in A\). Now \[\begin{eqnarray*} & & \addlength(n,\cons(a,\cons(b,x))) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-addlength-cons} = & \addlength(\next(n),\cons(b,x)) \\ & \hyp{\addlength(k,\cons(b,x)) = \next(\addlength(k,x))} = & \next(\addlength(\next(n),x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-addlength-cons} = & \next(\addlength(n,\cons(b,x))) \end{eqnarray*}\] as needed.
_test_addlength_snoc_next
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test (n -> a -> t a -> Bool)
_test_addlength_snoc_next _ _ =
testName "addlength(n,snoc(a,x)) == next(addlength(n,x))" $
\n a x ->
eq (addlength n (snoc a x)) (next (addlength n x))
_test_addlength_cons_next
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test (n -> a -> t a -> Bool)
_test_addlength_cons_next _ _ =
testName "addlength(n,cons(a,x)) == next(addlength(n,x))" $
\n a x ->
eq (addlength n (cons a x)) (next (addlength n x))
\(\addlength\) interacts with \(\rev\).
Let \(A\) be a set. For all \(n \in \nats\) and \(x \in \lists{A}\), we have \[\addlength(n,\rev(x)) = \addlength(n,x).\]
We proceed by list induction on \(x\). For the base case \(x = \nil\), we have \[\begin{eqnarray*} & & \addlength(n,\rev(\nil)) \\ & \href{/posts/arithmetic-made-difficult/Reverse.html#cor-rev-nil} = & \addlength(n,\nil) \end{eqnarray*}\] as needed. For the inductive step, suppose the equality holds for all \(n\) for some \(x\), and let \(a \in A\). Now \[\begin{eqnarray*} & & \addlength(n,\rev(\cons(a,x))) \\ & \href{/posts/arithmetic-made-difficult/Reverse.html#cor-rev-cons} = & \addlength(n,\snoc(a,\rev(x))) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-addlength-snoc-next} = & \next(\addlength(n,\rev(x))) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-addlength-rev} = & \next(\addlength(n,x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-addlength-cons-next} = & \addlength(n,\cons(a,x)) \end{eqnarray*}\] as needed.
Now we define \(\length\) as follows.
Let \(A\) be a set. Define \(\length : \lists{A} \rightarrow \nats\) by \[\length(x) = \addlength(\zero,x).\]
In Haskell:
Although \(\length\) is essentially defined as a left fold, it can be characterized as a right fold.
Let \(A\) be a set. Then we have \[\length(x) = \foldr(\zero)(\flip(\compose(\const)(\next)))(x).\]
We proceed by list induction on \(x\). For the base case \(x = \nil\), we have \[\begin{eqnarray*} & & \foldr(\zero)(\flip(\compose(\const)(\next)))(\nil) \\ & \href{/posts/arithmetic-made-difficult/Lists.html#def-foldr-nil} = & \zero \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-addlength-nil} = & \addlength(\zero,\nil) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-length} = & \length(\nil) \end{eqnarray*}\] as needed. For the inductive step, suppose the equality holds for some \(x\) and let \(a \in A\). Now \[\begin{eqnarray*} & & \foldr(\zero)(\flip(\compose(\const)(\next)))(\cons(a,x)) \\ & \href{/posts/arithmetic-made-difficult/Lists.html#def-foldr-cons} = & \flip(\compose(\const)(\next))(a,\foldr(\zero)(\flip(\compose(\const)(\next)))(x)) \\ & \hyp{\length = \foldr(\zero)(\flip(\compose(\const)(\next)))} = & \flip(\compose(\const)(\next))(a,\length(x)) \\ & \href{/posts/arithmetic-made-difficult/Flip.html#def-flip} = & \compose(\const)(\next)(\length(x),a) \\ & \href{/posts/arithmetic-made-difficult/Functions.html#def-compose} = & \const(\next(\length(x)))(a) \\ & \href{/posts/arithmetic-made-difficult/Functions.html#def-const} = & \next(\length(x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-length} = & \next(\addlength(\zero,x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-addlength-cons-next} = & \addlength(\zero,\cons(a,x)) \end{eqnarray*}\] as needed.
Since \(\length\) is equivalent to a right fold, it is the unique solution to a system of functional equations.
Let \(A\) be a set. \(\length\) is the unique solution \(f : \lists{A} \rightarrow \nats\) to the following system of equations for all \(a \in A\) and \(x \in \lists{A}\). \[\left\{\begin{array}{l} f(\nil) = \zero \\ f(\cons(a,x)) = \next(f(x)) \end{array}\right.\]
_test_length_nil
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test Bool
_test_length_nil t k =
testName "length(nil) == zero" $
eq (length (nil `withTypeOf` t)) (zero `withTypeOf` k)
_test_length_cons
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test (a -> t a -> Bool)
_test_length_cons _ k =
testName "length(cons(a,x)) == next(length(x))" $
\a x -> eq
((length (cons a x)) `withTypeOf` k)
(next (length x))
Special cases.
For all \(a,b \in A\), we have:
- \(\length(\cons(a,\nil)) = \next(\zero)\).
- \(\length(\cons(a,\cons(b,\nil))) = \next(\next(\zero))\).
- We have \[\begin{eqnarray*} & & \length(\cons(a,\nil)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-length-cons} = & \next(\length(\nil)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-length-nil} = & \next(\zero) \end{eqnarray*}\] as claimed.
- Note that \[\begin{eqnarray*} & & \length(\cons(a,\cons(b,\nil))) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-length-cons} = & \next(\length(\cons(b,\nil))) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-length-singleton} = & \next(\next(\zero)) \end{eqnarray*}\] as claimed.
_test_length_single
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test (a -> Bool)
_test_length_single t k =
testName "length(cons(a,nil)) == next(zero)" $
\a -> eq
((next zero) `withTypeOf` k)
(length (cons a (nil `withTypeOf` t)))
_test_length_double
:: (List t, Equal (t a), Natural n, Equal n)
=> t a -> n -> Test (a -> a -> Bool)
_test_length_double t k =
testName "length(cons(a,cons(b,nil))) == next(next(zero))" $
\a b -> eq
((next (next zero)) `withTypeOf` k)
(length (cons a (cons b (nil `withTypeOf` t))))
\(\length\) interacts with \(\snoc\).
For all \(a \in A\) and \(x \in \lists{A}\), we have \[\length(\snoc(a,x)) = \next(\length(x)).\]
We have \[\begin{eqnarray*} & & \length(\snoc(a,x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-length} = & \addlength(\zero,\snoc(a,x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-addlength-snoc-next} = & \next(\addlength(\zero,x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-length} = & \next(\length(x)) \end{eqnarray*}\] as claimed.
\(\length\) is invariant over \(\rev\).
Let \(A\) be a set. For all \(x \in \lists{A}\) we have \[\length(\rev(x)) = \length(x).\]
Note that \[\begin{eqnarray*} & & \length(\rev(x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-length} = & \addlength(\zero,\rev(x)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-addlength-rev} = & \addlength(\zero,x) \\ & \href{/posts/arithmetic-made-difficult/Length.html#def-length} = & \length(x) \end{eqnarray*}\] as claimed.
\(\length\) turns \(\cat\) into \(\nplus\).
Let \(A\) be a set. For all \(x,y \in \lists{A}\) we have \[\length(\cat(x,y)) = \nplus(\length(x),\length(y)).\]
We proceed by list induction on \(y\). For the base case \(y = \nil\), note that \[\begin{eqnarray*} & & \length(\cat(x,\nil)) \\ & \href{/posts/arithmetic-made-difficult/Cat.html#thm-cat-nil-right} = & \length(x) \\ & \href{/posts/arithmetic-made-difficult/Plus.html#thm-plus-zero-right} = & \nplus(\length(x),\zero) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-length-nil} = & \nplus(\length(x),\length(\nil)) \end{eqnarray*}\] as needed. For the inductive step, suppose the equality holds for some \(y \in \lists{A}\) and let \(a \in A\). Now \[\begin{eqnarray*} & & \length(\cat(x,\cons(a,y))) \\ & \href{/posts/arithmetic-made-difficult/Cat.html#thm-cat-snoc-left} = & \length(\cat(\snoc(a,x),y)) \\ & \hyp{\length(\cat(z,y)) = \nplus(\length(z),\length(y))} = & \nplus(\length(\snoc(a,x)),\length(y)) \\ & \href{/posts/arithmetic-made-difficult/Length.html#thm-length-snoc} = & \nplus(\next(\length(x)),\length(y)) \\ & = & \nplus(\length(x),\next(\length(y))) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-length-cons} = & \nplus(\length(x),\length(\cons(a,y))) \end{eqnarray*}\] as needed.
And \(\length\) detects \(\nil\).
Let \(A\) be a set with \(x \in \lists{A}\). Then \(x = \nil\) if and only if \(\length(x) = 0\).
We’ve already seen that \(\length(\nil) = \zero\). Suppose then that \(x = \cons(a,u)\); then \[\begin{eqnarray*} & & \length(x) \\ & \hyp{x = \cons(a,u)} = & \length(\cons(a,u)) \\ & = & \next(\length(u)); \end{eqnarray*}\] in particular, \(\length(x) \neq \zero\).
Testing
Suite:
_test_length ::
( TypeName a, Show a, Equal a, Arbitrary a
, TypeName n, Natural n, Equal n, Show n, Arbitrary n
, TypeName (t a), List t, Equal (t a), Show (t a), Arbitrary (t a)
) => Int -> Int -> t a -> n -> IO ()
_test_length size cases t n = do
testLabel2 "length" t n
let args = testArgs size cases
runTest args (_test_addlength_nil_right t n)
runTest args (_test_addlength_cons_right t n)
runTest args (_test_addlength_snoc_next t n)
runTest args (_test_addlength_cons_next t n)
runTest args (_test_addlength_rev t n)
runTest args (_test_length_foldr t n)
runTest args (_test_length_nil t n)
runTest args (_test_length_cons t n)
runTest args (_test_length_single t n)
runTest args (_test_length_double t n)
runTest args (_test_length_snoc t n)
runTest args (_test_length_rev t n)
runTest args (_test_length_cat t n)
runTest args (_test_length_zero t n)
Main: