1 MathReview


1.1 Readings

Discrete Mathematics using a Comptuter (O’Donnell, Hall, Page) Chapter 2

Discrete Mathematics with Applications - Metric Edition (Epp) Chapter 1

https://runestone.academy/ns/books/published/DiscreteMathText/chapter1.html
https://runestone.academy/ns/books/published/DiscreteMathText/variables1-1.html
https://runestone.academy/ns/books/published/DiscreteMathText/lang_sets1-2.html
https://runestone.academy/ns/books/published/DiscreteMathText/lang_relations1-3.html

https://www.cs.carleton.edu/faculty/dln/book/ch02_basic-data-types_2021_October_05.pdf

You should have complete mastery of all of these topics:
https://en.wikipedia.org/wiki/Arithmetic
https://en.wikipedia.org/wiki/Logarithm
https://en.wikipedia.org/wiki/Exponentiation
https://en.wikipedia.org/wiki/Nth_root

You should already know the topics outlined here quite well:
https://en.wikipedia.org/wiki/Elementary_algebra

You should be aware of the types of numbers:
https://en.wikipedia.org/wiki/Number

And have a basic understanding of these ideas:
https://en.wikipedia.org/wiki/Set_(mathematics)
https://en.wikipedia.org/wiki/Set_theory
https://en.wikipedia.org/wiki/Function_(mathematics)
https://en.wikipedia.org/wiki/Relation_(mathematics)

1.2 Summary images

1.2.1 Basic number systems

MathReview/number-system-0.png
MathReview/number-system-1.png
MathReview/number-system-2.png
MathReview/number-system-3.png

1.2.2 Elementary arithmetic

MathReview/exp-1.png
MathReview/exp-0.png

MathReview/log-0.jpg
MathReview/log-1.gif
MathReview/log-2.jpg

MathReview/exp-log-0.png
MathReview/exp-log-1.jpg

1.2.3 Elementary algebra

MathReview/alg-0.png
MathReview/alg-2.png
MathReview/alg-1.gif

1.2.4 Set theory

MathReview/set-0.png
MathReview/set-1.jpg

1.2.5 Relations and functions

MathReview/relations-functions-0.png
MathReview/relations-functions-1.jpg
MathReview/relations-functions-2.jpg

1.3 Equational reasoning with Haskell

https://wiki.haskell.org/Equational_reasoning_examples
https://www.haskellforall.com/2013/12/equational-reasoning.html
https://www.haskellforall.com/2014/07/equational-reasoning-at-scale.html
https://theory.stanford.edu/~blynn/haskell/eqreason.html

An equation x = y says that x and y have the same value.
Any time you see one of them you can replace it by the other.
Such a replacement is called a substitution.
https://en.wikipedia.org/wiki/Substitution_(logic)#Mathematics
Exactly the same technique can be used to reason about Haskell programs,
because equations in Haskell are true mathematical equations;
they are not assignment statements.

MathReview/EquationalReasoning.hs

#!/usr/bin/runghc

module EquationalReasoning where

-- Equational reasoning with functions

-- | We can use substitution to reason here:
-- >>> func1 3
-- 5
func1 :: Integer -> Integer
func1 z = 8 - z

-- | We can use substitution to reason using both funcs:
-- >>> func2 3 4
-- 20
func2 :: Integer -> Integer -> Integer
func2 x y = (2 + x) * func1 y

-- Equational reasoning with lists
-- Proofs upcoming in Induction lectures.

xs = [1, 2, 3]

ys = [3, 4, 5]

-- | Theorem 1
-- >>> theoremOne
-- True
theoremOne = length (xs ++ ys) == length xs + length ys

-- This is a function that adds 5 to a number.
f :: Int -> Int
f = (+) 5

-- | This is a home-made version of the `map` in Prelude.
-- >>> myMap f xs
-- [6,7,8]
myMap :: (a -> b) -> [a] -> [b]
myMap _ [] = []
myMap f (x : xs) = f x : myMap f xs

-- The real version is available by default in the default haskell Prelude.
-- https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#v:map
-- https://hackage.haskell.org/package/ghc-internal-9.1201.0/docs/src/GHC.Internal.Base.html#map

-- | Theorem 2
-- >>> theoremTwo
-- True
theoremTwo = length (map f xs) == length xs

-- | Theorem 3
-- >>> theoremThree
-- True
theoremThree = map f (xs ++ ys) == map f xs ++ map f ys

-- | Theorem 4. See lecture notes for two proofs of this one.
-- >>> theoremFour
-- True
theoremFour = length (map f (xs ++ ys)) == length xs + length ys

1.3.1 Theorem 1 (length (++)).

The following theorem states an obvious result,
the length of a concatenation,
is the sum of the lengths of the pieces that were concatenated.
This theorem can be proved using list induction (upcoming).
We can use the theorem directly to justify equational reasoning steps,
and an example is given below.

Let xs, ys :: [a] be arbitrary lists.
Then length (xs ++ ys) == length xs + length ys.

1.3.2 Theorem 2 (length map).

The next theorem says that if you map a function over a list,
the list of results is the same size as the list of arguments.

Let xs :: [a] be an arbitrary list,
and f :: a -> b an arbitrary function.
Then length (map f xs) == length xs.

1.3.3 Theorem 3 (map (++)).

We also need the following result,
which says that if you map a function over a concatenation of two lists,
the result could also be obtained by concatenating separate maps over the original lists.
This theorem is frequently used in parallel functional programming,
because it shows how to decompose a large problem,
into two smaller ones than can be solved independently.

Let xs, ys :: [a] be arbitrary lists,
and let f :: a -> b be an arbitrary function.
Then map f (xs ++ ys) == map f xs ++ map f ys.

1.3.4 Theorem 4.

To illustrate how a theorem can be proved using direct equational reasoning,
we will give a proof for this theorem:

For arbitrary lists xs, ys :: [a],
and arbitrary f :: a -> b,
the following equation holds:
length (map f (xs ++ ys)) == length xs + length ys.

Proof.
We prove the theorem by equational reasoning,
starting with the left hand side of the equation,
and transforming it into the right hand side.

length (map f (xs ++ ys))
  = length (map f xs ++ map f ys)           { map (++) }
  = length (map f xs) + length (map f ys)   { length (++) }
  = length xs + length ys                   { length map }

There are usually many ways to prove a theorem; here is an alternative version:

length (map f (xs ++ ys))
  = length (xs ++ ys)           { length map }
  = length xs + length ys       { length (++) }

Either proof is fine, and you don’t need to give more than one!
It is a matter of taste and opinion as to which proof is better.
Sometimes a shorter proof seems preferable to a longer one,
but issues of clarity and elegance may also affect your choice.

You may be wondering why you haven’t used equational reasoning for years,
as it is so simple and powerful.
The reason is that equational reasoning requires equations,
which say that two things are equal.

Imperative programming languages simply don’t contain any equations.
Consider the following code, which might appear in a C or Java program:
n = n + 1
That might look like an equation, but it isn’t!
It is an assignment.
The meaning of the statement is a command:

“fetch the value of n, then add 1 to it,
then store it in the memory location called n,
discarding the previous value.”

This is not a claim that n has the same value as n + 1.

Because of this, many imperative programming languages use a distinctive notation for assignments.
For example, Algol and its descendants use the := operator:
n := n + 1

What is different about pure functional programming languages like Haskell,
is that x = y really is an equation, stating that x and y have the same value.
Furthermore, Haskell has a property called referential transparency.
This means that if you have an equation x = y,
then you can replace any occurrence of x by y,
and you can replace any occurrence of y by x.
This is, of course, subject to various restrictions;
you have to be careful about names and scopes,
and grouping with parentheses, etc.

The imperative assignment statement n := n + 1 increases the value of n.

Whereas the Haskell equation n = n + 1 defines n to be the solution to the mathematical equation.

This equation has no solution, so the result is that n is undefined.
If the program ever uses an undefined value like n,
then the computer will either give an error message or go into an infinite loop.
Assignments have to be understood in the context of time passing as a program executes.
To understand the n := n + 1 assignment,
you need to talk about the old value of the variable, and its new value.

In contrast, equations are timeless,
as there is no notion of changing the value of a variable.

Indeed, there is something peculiar about the word “variable”.
In mathematics and also in functional programming,
a variable does not vary at all!
To vary means to change over time, and this simply does not happen.
A mathematical equation like x = 23 means that x has the value 8,
and it will still have that value tomorrow.

If the next chapter says x = 2 + 2,
then this is a completely different variable being defined,
which just happens to have the same name;
it doesn’t mean that 8 shrank over the course of a few pages.

A variable in mathematics means “a name that stands for some value”,
while a variable in an imperative programming language means
“a memory location whose value is modified by certain instructions”.

As one would expect, there is a price to be paid for the enormous benefits of equational reasoning.
Pure functional languages like Haskell, as well as mathematics itself,
are demanding in that they require you to think through a problem deeply,
in order to express its solution with equations.
It’s always possible to hack an imperative program,
by sticking an assignment in it somewhere in order to patch up a problem,
but you cannot do that in a functional program.
On the other hand, if our goal is to build correct and reliable software,
and this should be our goal,
then the discipline of careful thought will be repaid in higher quality software!