Range

Posted on 2017-05-05 by nbloomf
Tags: arithmetic-made-difficult, literate-haskell

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.\]

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:

main_range :: IO ()
main_range = do
  _test_range 20 100 (nil :: ConsList Unary)