Range
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 Range
( range, _test_range, main_range
) where
import Testing
import Tuples
import DisjointUnions
import NaturalNumbers
import Plus
import Lists
import Snoc
import Cat
import Length
import Map
import UnfoldN
For our first application of \(\unfoldN(\ast)\) we’ll define a function, \(\range\), that constructs lists of natural numbers. There are a few ways to do this. We could take an argument \(n\) and construct the list of natural numbers from \(\zero\) to \(n\), but this is too specialized. We could instead take two arguments \(a\) and \(b\) and construct the list of natural numbers from \(a\) to \(b\), but we’ll have to check whether or not the arguments are in order. A third option – and the one we’ll take – is to take two arguments \(a\) and \(b\), and construct the list of the first \(b\) natural numbers starting from \(a\).
Define \(f : \nats \rightarrow \ast + \nats \times \nats\) by \[f(k) = (\next(k),k).\] We then define \(\range : \nats \times \nats \rightarrow \lists{\nats}\) by \[\range(a,b) = \unfoldN(f,b,a).\]
In Haskell:
Since \(\range\) is an \(\unfoldN(\ast)\), it is the unique solution to a system of functional equations.
\(\range\) is the unique \(f : \nats \times \nats \rightarrow \lists{\nats}\) satisfying the following system of equations for all \(a,b \in \nats\). \[\left\{\begin{array}{l} f(a,\zero) = \nil \\ f(a,\next(b)) = \cons(a,f(\next(a),b)) \end{array}\right.\]
_test_range_zero :: (List t, Natural n, Equal (t n), Equal n)
=> t n -> Test (n -> Bool)
_test_range_zero t =
testName "range(a,zero) == nil" $
\a -> eq (range a zero) (nil `withTypeOf` t)
_test_range_next :: (List t, Natural n, Equal (t n), Equal n)
=> t n -> Test (n -> n -> Bool)
_test_range_next t =
testName "range(a,next(b)) == cons(a,range(next(a),b))" $
\a b -> eq
((range a (next b)) `withTypeOf` t)
(cons a (range (next a) b))
A special case.
For all \(a \in \nats\) we have \(\range(a,\next(\zero)) = \cons(a,\nil)\).
Note that \[\begin{eqnarray*} & & \range(a,\next(\zero)) \\ & = & \cons(a,\range(\next(a),\zero)) \\ & = & \cons(a,\nil) \end{eqnarray*}\] as claimed.
The \(\length\) of a \(\range\) is predictable.
For all \(a,b \in \nats\), we have \(\length(\range(a,b)) = b\).
We proceed by induction on \(b\). For the base case \(b = \zero\), we have \[\begin{eqnarray*} & & \length(\range(a,b)) \\ & = & \length(\range(a,\zero)) \\ & = & \length(\nil) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-length-nil} = & \zero \\ & = & b \end{eqnarray*}\] as claimed. For the inductive step, suppose the equality holds for some \(b\). Now \[\begin{eqnarray*} & & \length(\range(a,\next(b))) \\ & = & \length(\cons(a,\range(\next(a),b))) \\ & \href{/posts/arithmetic-made-difficult/Length.html#cor-length-cons} = & \next(\length(\range(\next(a),b))) \\ & = & \next(b) \end{eqnarray*}\] as claimed.
\(\range\) interacts with \(\snoc\).
For all \(a,b \in \nats\) we have \[\range(a,\next(b)) = \snoc(\nplus(a,b),\range(a,b)).\]
We proceed by induction on \(b\). For the base case \(b = \zero\), we have \[\begin{eqnarray*} & & \range(a,\next(b)) \\ & = & \range(a,\next(\zero)) \\ & = & \cons(a,\nil) \\ & \href{/posts/arithmetic-made-difficult/Snoc.html#cor-snoc-nil} = & \snoc(a,\nil) \\ & = & \snoc(\nplus(a,\zero),\range(a,\zero)) \\ & = & \snoc(\nplus(a,b),\range(a,b)) \end{eqnarray*}\] as claimed. For the inductive step, suppose the equality holds for some \(b\). Then we have \[\begin{eqnarray*} & & \range(a,\next(\next(b))) \\ & = & \cons(a,\range(\next(a),\next(b))) \\ & = & \cons(a,\snoc(\nplus(\next(a),b),\range(\next(a),b))) \\ & \href{/posts/arithmetic-made-difficult/Snoc.html#cor-snoc-cons} = & \snoc(\nplus(\next(a),b),\cons(a,\range(\next(a),b))) \\ & = & \snoc(\nplus(a,\next(b)),\range(a,\next(b))) \end{eqnarray*}\] as needed.
\(\range\) interacts with \(\nplus\) in its second argument.
If \(a,b,c \in \nats\), we have \[\range(a,\nplus(b,c)) = \cat(\range(a,b),\range(\nplus(a,b),c)).\]
We proceed by induction on \(c\). For the base case \(c = \zero\), we have \[\begin{eqnarray*} & & \range(a,\nplus(b,c)) \\ & = & \range(a,\nplus(b,\zero)) \\ & \href{/posts/arithmetic-made-difficult/Plus.html#thm-plus-zero-right} = & \range(a,b) \\ & = & \cat(\range(a,b),\nil) \\ & = & \cat(\range(a,b),\range(\nplus(a,b),\zero)) \\ & = & \cat(\range(a,b),\range(\nplus(a,b),c)) \end{eqnarray*}\] as claimed. For the inductive step, suppose the equality holds for some \(c\). Using the inductive hypothesis we have \[\begin{eqnarray*} & & \range(a,\nplus(b,\next(c))) \\ & \href{/posts/arithmetic-made-difficult/Plus.html#thm-plus-next-right} = & \range(a,\next(\nplus(b,c))) \\ & = & \snoc(\nplus(a,\nplus(b,c)),\range(a,\nplus(b,c))) \\ & = & \snoc(\nplus(a,\nplus(b,c)),\cat(\range(a,b),\range(\nplus(a,b),c))) \\ & \href{/posts/arithmetic-made-difficult/Plus.html#thm-plus-associative} = & \snoc(\nplus(\nplus(a,b),c),\cat(\range(a,b),\range(\nplus(a,b),c))) \\ & = & \cat(\range(a,b),\snoc(\nplus(\nplus(a,b),c),\range(\nplus(a,b),c))) \\ & = & \cat(\range(a,b),\range(\nplus(a,b),\next(c))) \end{eqnarray*}\] as needed.
\(\range\) interacts with \(\next\) in its first argument.
For all \(a,b \in \nats\) we have \[\range(\next(a),b) = \map(\next)(\range(a,b)).\]
We proceed by induction on \(b\). For the base case \(b = \zero\), we have \[\begin{eqnarray*} & & \range(\next(a),b) \\ & = & \range(\next(a),\zero) \\ & = & \nil \\ & \href{/posts/arithmetic-made-difficult/Map.html#cor-map-nil} = & \map(\next)(\nil) \\ & = & \map(\next)(\range(a,\zero)) \\ & = & \map(\next)(\range(a,b)) \end{eqnarray*}\] as needed. For the inductive step, suppose the equality holds for some \(b\). Now \[\begin{eqnarray*} & & \range(\next(a),\next(b)) \\ & = & \cons(\next(a),\range(\next(\next(a)),b)) \\ & = & \cons(\next(a),\map(\next)(\range(\next(a),b))) \\ & \href{/posts/arithmetic-made-difficult/Map.html#cor-map-cons} = & \map(\next)(\cons(a,\range(\next(a),b))) \\ & = & \map(\next)(\range(a,\next(b))) \end{eqnarray*}\] as needed.
And \(\range\) interacts with \(\nplus\) in its first argument. In this theorem we use a bit of new notation. When a function argument is replaced by a dash, we implicitly mean lambda-abstraction. That is, if \(f\) is a function, then \(f(-)\) is short for \(\lambda x. f(x)\).
If \(a,b,c \in \nats\), we have \[\range(\nplus(a,b),c) = \map(\nplus(a,-))(\range(b,c)).\]
We proceed by induction on \(a\). For the base case \(a = \zero\) note that \(\nplus(a,-) = \id\), since for all \(b \in \nats\) we have \[\nplus(\zero,-)(b) = \nplus(\zero,b) = b.\] Thus \[\begin{eqnarray*} & & \range(\nplus(a,b),c) \\ & = & \range(\nplus(\zero,b),c) \\ & \href{/posts/arithmetic-made-difficult/Plus.html#cor-plus-up-zero} = & \range(b,c) \\ & = & \map(\id)(\range(b,c)) \\ & = & \map(\nplus(\zero,-))(\range(b,c)) \\ & = & \map(\nplus(a,-))(\range(b,c)) \end{eqnarray*}\] as needed. For the inductive step, suppose the result holds for some \(a\). Now \[\begin{eqnarray*} & & \range(\nplus(\next(a),b),c) \\ & \href{/posts/arithmetic-made-difficult/Plus.html#cor-plus-up-next} = & \range(\next(\nplus(a,b)),c) \\ & = & \map(\next)(\range(\nplus(a,b),c)) \\ & = & \map(\next)(\map(\nplus(a,-))(\range(b,c))) \\ & = & (\map(\next) \circ \map(\nplus(a,-)))(\range(b,c)) \\ & = & \map(\next(\nplus(a,-)))(\range(b,c)) \\ & = & \map(\nplus(\next(a),-))(\range(b,c)) \end{eqnarray*}\] as needed.
Testing
Suite:
_test_range ::
( TypeName n, Natural n, Equal n, Show n, Arbitrary n
, TypeName (t n), List t, Equal (t n), Show (t n), Arbitrary (t n)
) => Int -> Int -> t n -> IO ()
_test_range size cases t = do
testLabel1 "range" t
let args = testArgs size cases
runTest args (_test_range_zero t)
runTest args (_test_range_next t)
runTest args (_test_range_one t)
runTest args (_test_range_length t)
runTest args (_test_range_snoc t)
runTest args (_test_range_plus_right t)
runTest args (_test_range_next_left t)
runTest args (_test_range_plus_left t)
Main: