#
#
It’s Thursday afternoon and the von der Surwitz siblings meet in their reserved study room at the main university library to go over Wednesday’s lecture and examples. After Wednesday’s class, they’re glad they and the other ItCS participants had met with Professor Chandra at the Novalis Tech Open House two weeks prior to the start of school and had gotten a heads-up on what was ahead, including her repository. As a result they all had gone down the minimal prerequisite rabbit holes and were more or less ready.
Ursula von der Surwitz has plugged in her laptop to the big monitor and is scrolling through Professor Chandra’s first substantive lecture on Wednesday of the first week of classes[fn:1].
⌜…
For most of us our first experience with the abstraction power of
numbers begins when our parents teach us to hold up three fingers to
show everyone we’re three years old. And so we were introduced to the
idea of seeing an abstract numerical magnitude such as the number
three as a mapping or connection of three fingers to three years of
life.
Then at some point we learn what mathematician Eugenia Cheng[fn:2] calls the “counting poem,” i.e., we learn how to count from one to ten, usually on our fingers. And about this time we learn to negotiate numerically, like when we said, I’ll give so many of these if you give me so many of that. But then begins formal school math, and each year math becomes progressively less interesting, less popular — to the point of being a hated subject. This in my opinion is due to the curriculum being based on conditioned learning, the when you see this, do this method … unfortunately.
But if you major in math in college you eventually start to see in the later years[fn:3] — after the four Freshman and Sophomore semesters of calculus, differential equations, and linear algebra — what is commonly called “higher math.” And these upper-semester math courses can be a complete reboot of math, weeding out vague, ad hoc, /parochial/[fn:4] notions, replacing them with a rigorous, theoretical, formalistic, foundational understanding of math. As mathematician Joe Fields says, this is when you stop being a “see this, do this” calculator and become a prover, i.e., a deeper thinker about math.
Set theory is a big part of this formalism[fn:5]. Set theory is an exacting, don’t-take-stuff-for-granted world, which turns out to be good for the computer world as well since computer circuits don’t attend human grade school, don’t learn poems, and don’t have fingers[fn:6]. Again, if you want a computer to understand something, you have to spell it out in very precise and exacting ways. That is to say, you’re always facing logical entailment (LE) with computers. So how then does a computer understand numbers? And isn’t a computer doing numbers the same way a clock does time? Think about it. Does a ticking clock that you have to wind up have any real concept of time? No, it doesn’t.
One fascinating twist of mathematical history is how, on the whole, the Greeks seemed to favor geometry over numbers. Their mastery of geometry really got going with Euclid’s Elements ca. 300 BC, which starts with just a point and a line and from there builds up expansive theorems about complex geometric shapes, i.e., no numbers[fn:7]. Even when Euclid’s geometry worked with the concepts of length and angles, no numbers were employed[fn:8]. And as the physicist Julian Barbour said, A triangle is known for its shape not its size.
This week we’ll talk about numbers in a fairly theoretical but not
really difficult manner. Along with the math, we’ll do some work with
Haskell that you should be ready for[fn:9].
…⌟
𝔘𝔯𝔰𝔲𝔩𝔞: So like
Professor Chandra said yesterday, we need to see things in terms of
set theory from here on out.
[murmurs of agreement] \
𝔘𝔴𝔢: And like she said,
with set theory we can properly define relations and function. All of
which sounds rather ominous. Like everything we thought we knew was
about to go away.\
[more agreement murmurs] \
𝔘𝔯𝔰𝔲𝔩𝔞: [continuing] So
here’s her breakdown of what she called number classification or
taxonomy. [scrolling…] \
⌜…
-
$\mathbb{N}\;$ : the natural counting numbers, often starting with $0\;$; otherwise, with$1\;$ . -
$\mathbb{Z}\;$ : whole number integers — just like$\mathbb{N}\;$ but with the positive numbers duplicated as negative numbers, along with$0\;$ between them. -
$\mathbb{Q}\;$ : the rational numbers — composed as$\frac{a}{b}\;$ where$a$ and$b$ are integers and$b$ cannot be$0\;$ — although this is really too simple and we’ll be expanding on it later. -
$\mathbb{R}\;$ : the real numbers are the limit of a convergent sequence of rational numbers… Really? Yes, but this is a higher math sort of definition. Suffice it to say for now reals are the rational numbers (including recurring decimals) along with ir-rational numbers (non-recurring decimals), e.g., the square root of$2\;$ . In other words, numbers that are expressed with decimals[fn:10]. Lots more to come… -
$\mathbb{C}\;$ : the complex numbers are of the form$a + bi\;$ where$a$ and$b$ are real numbers and$i$ is the square root of$-1\;$ . Again, lots more later.
…⌟
𝔘𝔴𝔢: Right. We’re not
supposed to worry about this too much yet; we’ll go into it more
later. Just realize that we’re talking about the set of natural
numbers, the set of real numbers, et cetera.
[murmurs of agreement] \
𝔘𝔱𝔢: Their traits and
properties, as she said. And the fact that each number “species” is a
subset of the next one up [writing on the whiteboard]
\begin{align*} \mathbb{N} ⊂ \mathbb{Z} ⊂ \mathbb{Q} ⊂ \mathbb{R}\ ⊂ \mathbb{C} \end{align*}
𝔘𝔱𝔢: [continuing] One
is contained by the other.
𝔘𝔴𝔢: [paging through
his written notes] Then she once more drove home the point that set
theory is important [quoting]
⌜…
Set theory is the basic foundation of math, and is fundamental to the
discrete math of computer science. But there’s a new kid is on the
block, namely, category theory, which is even more foundational than
set theory. With Haskell we’ll encounter some of the rudiments of
category theory now and then. \
…⌟
𝔘𝔴𝔢: Anybody know what
that means?
𝔘𝔯𝔰𝔲𝔩𝔞: We all watched
the video? [clicking on this YouTube link] Did you all watch it? \
𝔘𝔱𝔢: Ahhh, yes, but I
can tell I’m a long way from really getting it, or why it’s such a big
deal. \
𝔘𝔴𝔢: So we see a bunch
of stuff we supposedly already know being repackaged in a way that
uncovers how we really only had a superficial understanding of the
concepts. \
[laughter] \
𝔘𝔯𝔰𝔲𝔩𝔞: I watched it,
but it’ll take some time to sink in because [nodding to Uwe] on one
level I guess I understood, but I don’t think I caught what the whole
point was[fn:11]. I suppose that’s coming as well. \
𝔘𝔱𝔢: But wasn’t it a
bit ominous the way she said this is higher math — if not grad
school stuff? But we’re not supposed to be intimidated! No! At least
not yet. \
[nods and laughter] \
𝔘𝔴𝔢: Like they’ve been
saying, we’re Versuchskaninchen. \
[laughter] \
𝔘𝔱𝔢: Guinea pigs. No,
but I’m getting the impression this could be a great course. \
[nods]
𝔘𝔯𝔰𝔲𝔩𝔞: [scrolling] So
we then come to Haskell’s version of the numbers.
[all study the screen] \
𝔘𝔱𝔢: Which is supposed
to be close to the whole mathematical taxonomy. \
⌜…
-
Int
: limited-precision integers in at least the range $[-229 , 229)\;$. -
Integer
: arbitrary-precision integers (read lots of integers, lots more thanInt
). -
Rational
: arbitrary-precision rational numbers. -
Float
: single-precision floating-point numbers. -
Double
: double-precision floating-point numbers -
Complex
: complex numbers as defined inData.Complex
.
…⌟
𝔘𝔯𝔰𝔲𝔩𝔞: And she tells
us not to worry about what precision and floating-point mean for
the time being.
𝔘𝔱𝔢: Right, because
they’re part of how the electrical logic circuits do numbers. The
whole “logical entailment” of doing numbers on an electric machine
thing. \
[murmurs of agreement] \
𝔘𝔴𝔢: And we’re not to
worry about natural numbers yet. We’ll do those separate. \
[murmurs of agreement] \
𝔘𝔯𝔰𝔲𝔩𝔞: [scrolling] So here’s more.
⌜…
A number is a concept, first and foremost a symbol related to
quantity and magnitude. And as such a number has great powers of
abstraction. Numbers may be applied in the abstraction exercises of
counting or enumerating, as well as measuring things[fn:12].
Consider these qualities of quantities
- cardinality, or how many?
- ordinality, or what order?
- enumeration, or, generally, how do we count out things?
As we’ve said, when we bring the computer into this quantification
game, we cannot assume these basic qualities as given[fn:13].
…⌟
𝔘𝔯𝔰𝔲𝔩𝔞: [continuing]
Any thoughts, questions?
[all study the screen] \
𝔘𝔱𝔢: Let’s go on.
⌜…
In everyday language cardinal numbers are simply the counting numbers,
\begin{align*}
\,Sstars \, | &= 3 \ |
\,\{a, b, c\}\, | &= 3 \ |
\,\mathbb{N}\, | &= ∞ \ |
\,\mathbb{Z}\, | &= ∞ |
\end{align*}
But why are we being so “conceptual” about the simple idea of amount,
and why must we give it a fancy name? Again, math likes to formalize
things, nail things down. Starting with exactness and precision we can
then build very complex and logically-based math[fn:15]. Now, if two
sets have the same cardinality, is this somehow significant? Above, we
see that both the set of natural numbers and the set of integers have
infinity as their cardinality. Are, therefore,
The bijection principle states that two sets have the same size if and only if there is a bijection (injective and surjective together) between them[fn:16].
Which means $|\,Sstars \,|\;$ and
…⌟
𝔘𝔴𝔢: So basically,
cardinality is a formalism from set theory about amount and
magnatude. Fair enough.
𝔘𝔯𝔰𝔲𝔩𝔞: What about this
section? [scrolling]
⌜…
The first thing to know about Haskell and set theory is, yes, we can
do real set theory with Haskell’s Data.Set
package. But as beginners
learning the ropes we will start with a simpler representation of sets
through Haskell’s basic list data structure. Realize, however, that
a list is not a set; they are two different beasts, and we’ll have to
account for that. For one, a set may have duplicates, whereas a list
holds a definite sequence of things, i.e., every element of the list
is unique, even if some elements are repeated[fn:18]. So the set
[1,2,1]
, [1,2]
, and [2,1,1]
are in
fact all different lists. Initially, we’ll practice set theory with
lists and build alternate set theory code based on lists. Then when we
understand “squirrel math” a little more, i.e., how to work with the
data structures known as trees, we’ll do proper set theory with
Haskell’s set theory module, Data.Set[fn:20]. For the immediate
future we’ll consider a list to be a beginner’s substitute for a
set. And so a simple “how many” function on a list standing in for a
set is length
length [1,2,3,4,5]
5
Again, more to come. Watch this space[fn:21].
…⌟
𝔘𝔴𝔢: So, bottom line,
we’re not going to have real sets with Haskell right away, just a sort
of Ersatz sets with lists. Are we all sort of familiar with Haskell
lists from the Learn You…?
[murmurs of affirmation as Ursula clicks on a tab showing LYAHFGG’s
section on lists] \
𝔘𝔱𝔢: So what about
finding another example of sets having duplicates and being in
different order? [looks back and forth] \
𝔘𝔯𝔰𝔲𝔩𝔞: Well, here’s
mine [scrolling to her personal annotation of the professors notes and
reading aloud]
⌜…
A “set” of students in a class pass around a sign-in sheet each day on
which they sign their name. Some days the names are in one order,
other days another order, depending on how it was passed around the
room. And sometimes a student accidentally signs twice. But it’s still
the same set of students, order and duplicate sign-ins
notwithstanding. \
…⌟
𝔘𝔴𝔢: Oooh,
notwithstanding! You’re getting fancy with your English.
[laughter] \
𝔘𝔯𝔰𝔲𝔩𝔞: Hey, no
brain-shaming! \
[laughter] \
𝔘𝔴𝔢: [continuing] No,
really, that’s a great example because it brings out the difference
between the actual set and how it’s being — represented. \
𝔘𝔱𝔢: So do we all
understand her side note about set unions? \
𝔘𝔴𝔢: I drew some Venn
diagrams [pulls out a hand drawing and places it on the table] and,
yes, you can see how all the overlaps are telling us not to worry
about the duplicates of the individual sets making up the union. \
𝔘𝔱𝔢: Right, and it’s
curious how when you just look at the set notations, you don’t
necessarily see that. At least I don’t. I mean [writing on the board]
\begin{align*} A ∪ B &= B ∪ A, \[.5em] A ∩ B &= B ∩ A, \[.5em] (A ∪ B) ∪ C &= A ∪ (B ∪ C), \quad and \[.5em] (A ∩ B) ∩ C &= A ∩ (B ∩ C) \end{align*}
𝔘𝔱𝔢: [continuing] The
first thing we have to know is what are union and intersection in the
world of lists? I figured out that putting lists together come in two
forms, the cons operation, and the concatenation operator.
𝔘𝔯𝔰𝔲𝔩𝔞: Right. Like
this. [creating a source block]
1 : []
[1]
𝔘𝔯𝔰𝔲𝔩𝔞: [continuing]
for cons
and then this for concatenation
[1] ++ [2]
𝔘𝔴𝔢: You showed me that
stackoverflow post where they created a union
function for
lists. But then one commentor noted how no, these list versions of
union didn’t allow for commutativity and all.
𝔘𝔯𝔰𝔲𝔩𝔞: Exactly. Only
the Data.Set
, the true set theory package did. \
[silence while digesting the ideas] \
𝔘𝔱𝔢: Yeah, right, I
looked through it, but I consider myself just a beginner with Haskell,
but it seemed to be talking mainly about how you eliminate
duplicates. \
𝔘𝔴𝔢: But what about
that “equational reasoning”? One of you guys went into that, didn’t
you? \
[Ursula and Ute trade glances] \
𝔘𝔱𝔢: Well, I looked it
up on Wikipedia and was redirected to an article that was actually
called Universal algebra [Ursula brings up the article on the
monitor], and in the Basic idea section \
𝔘𝔴𝔢: Once again, on
that fateful day when we sat down and talked with Professor Chandra
she told us to not fear the rabbit hole! \
[laughter] \
𝔘𝔱𝔢: And to expect a
whole new way of seeing math. \
𝔘𝔴𝔢: Right, that didn’t
present itself serially, one thing after another. No, we’re taking
this on in parallel. \
[murmurs of agreement] \
𝔘𝔱𝔢: I did email
Professor Chandra, and she said the Universal algebra page was not
very specific for what Haskell meant by equational reasoning — and
she gave me another link, which we can look at — but that it
contained a lot of good stuff, and we should try to get the gist of
it. \
[rueful murmurs] \
𝔘𝔱𝔢: [continuing,
reading from her laptop]
⌜…
An algebraic structure consists of a nonempty set
𝔘𝔯𝔰𝔲𝔩𝔞: [bringing the page up on the monitor] Here it is.
import Data.Set (Set, lookupMin, lookupMax)
import qualified Data.Set as Set
Set.union (Set.fromList [1, 3, 5, 7]) (Set.fromList [0, 2, 4, 6])
fromList [0,1,2,3,4,5,6,7]
Set.union (Set.fromList [0, 2, 4, 6]) (Set.fromList [1, 3, 5, 7])
fromList [0,1,2,3,4,5,6,7]
Set.empty
fromList []
Set.union (fromList [1,2,3]) (Set.empty)
fromList [1,2,3]
Set.intersection (fromList [1,2,3]) (Set.empty)
fromList []
import Data.Ratio
𝔘𝔯𝔰𝔲𝔩𝔞: Let’s leave it for now. So moving on [scrolling].
⌜…
In everyday language the concept of order is conveyed when we say
first, second, third, fourth, etc[fn:22]. And we’re done, right? But
again, as seen from set theory — as well as from the computer world
— (re-)establishing order is important and cannot always be taken
for granted. In fact, a great deal of investigation surrounds
ordinality.
The counting numbers, the set
But order, and all it’s implications, is not always a given in real life. What if we wrote the numbers from one to ten on small squares of paper, put them in a box, and then shook them out on the floor in a straight line? Would they be in order? Chances are, no. And what about sets of things that aren’t inherently numerical, such as colors?
So if we don’t have things in proper order—which is often the case in the real world—we have to put things in order ourselves. And that means we will need to sort a set of unsorted elements into some order. Sorting, in fact, is one of the more basic tasks computers do in the everyday world[fn:24].
But to sort we need to compare things. Obviously, ten whole numbers written clearly on ten squares of paper can be easily sorted by hand[fn:25]. But what if we had thousands of squares of paper, each with a unique number? Then we’d have a long task ahead of us. But no matter how big or small the task, we would compare two numbers at a time and then make a judgement based on whether one number was
- greater than
- equal to
- less than
the other number, then rearranging as needed.
…⌟
Haskell is a typed language with a feature called /type classes/[fn:26]. A Haskell type class encompasses traits, patterns, concepts, or what we might call a certain “-ness” that can be imparted to data types. In Haskell, for example, numbers and the characters of the alphabet can be directly compared for “equal-ness,” and when we test at the ghci REPL prompt, this seems to be built-in
((5 == 3) || ('a' == 'a')) || ('b' == 'B')
True
Mindful of LE, we see that Haskell’s Eq
class
housing this equal-ness must have some sort of behind-the-scenes
mechanism that allows us to take two things, analyze them, then return
a decision, true or false (yes they are equal, no they’re not equal),
on whether two said things were equal or not — all of this just for
doing some typing, e.g., 5 == 3
into the REPL.
Something else to consider is how some things don’t necessarily have
the concept of equal or not equal straight out of the box. What if we
created a data type for colors and we wanted to compare the individual
color values for equal-ness? Intuitively, we might say red, yellow,
and blue are equal since they are all primary colors; likewise,
orange, green, and violet are equal because they’re the secondary
colors. But what if we just say each color is equal to itself and not
equal to any of the others? … So how would we establish equal-ness
for colors? If we want to type Green == Blue
into the REPL it’s up
to us to have created some sort of equal-ness and told Haskell about
it.
Data types that need to establish equal-ness for themselves can apply
to the Haskell Eq
type class for membership. Type class membership
is registered with an instance statement. To see all the data types
that have an instance registered with Eq
we can use :info
(abbreviated :i
) at the REPL[fn:27]
:i Eq
type Eq :: * -> Constraint class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool {-# MINIMAL (==) | (/=) #-} -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ ... instance Eq Int -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Eq Float -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Eq Double -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Eq Char -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Eq Bool -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ ...
which gives us a long list (abbreviated above) of data types that have
equal-ness defined[fn:28]. Notice the two “prescribed” methods (functions)
(==)
and (/=)
. These functions are the mechanism used by Eq
to
establish equal-ness. They must be custom defined in each instance
declaration in order for a data type to have its own established
equal-ness[fn:29]. And so if you create a new type, you, the
programmer, must come up with your own version of these two functions,
(==)
and (/=)
to establish the trait, the property of equal-ness
for that new data type. As LYAHFGG notes, the type signature for the
equal-ness function (==)
is[fn:30]
:t (==)
which indicates (==)
takes two inputs of some type a
and returns a
Bool
type, i.e., either True
or False
. Good, we saw how that
works with our examples above. But there’s another wrinkle to this
story. So if (==)
is a function that takes two objects of type a
,
e.g., two integers, real numbers, characters, booleans, etc., how can
we use this same symbol (==)
in so many different contexts? Looking
above, how did Haskell know to use integer equal-ness rules (defined
by instance Eq Int
) when comparing integers, and then letter
equal-ness rules (defined by instance Eq Char
) when comparing
letters? This feat is what we call /ad hoc polymorphism/[fn:31], which
allows just a single function symbol, e.g., (==)
, to be used
(overloaded) in many different contexts. And so Haskell figures out
behind the scenes which instance to apply. Neat.
Let’s take a closer look at a color type by defining our own[fn:32] [fn:33]
data Color = Red | Yellow | Blue | Green deriving (Show,Read)
Obviously, the colors red, yellow, blue, and green have no intrinsic
numerical properties by which to compare one with the other. However,
Haskell’s type class system still allows us to create some manner of
equal-ness for it. And so we write this version of an instance of
Eq
for Color
instance Eq Color where
Red == Red = True -- could also be (==) Red Red = True
Yellow == Yellow = True
Blue == Blue = True
Green == Green = True
_ == _ = False -- anything getting to this point must be false
And so we’ve hand-coded our own equal-ness for Color
by spelling out
how (==)
works for Color
. This literally tells Haskell what
Color
equal-ness should be by customizing how the function (==)
should work. Let’s have another look at the type signature for (==)
:t (==)
As LYAHFGG notes, the Eq a
part is known as a class
constraint. This means whatever a
might be[fn:34], it must have an
equal-ness instance already registered for it — which we do —
otherwise, Haskell won’t know how to compare two things of a
for
equal-ness[fn:35].
(Red == Red) && (Red /= Green)
True
Good, it’s working and we now can compare Color
values for
equal-ness, but how do we order colors? No matter how we establish
one color before the other, it might seem arbitrary, but we can do it
if we want. For basic order-ness Haskell has the type class Ord
to
which types can register
:i Ord
type Ord :: * -> Constraint class Eq a => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool (<=) :: a -> a -> Bool (>) :: a -> a -> Bool (>=) :: a -> a -> Bool max :: a -> a -> a min :: a -> a -> a {-# MINIMAL compare | (<=) #-} -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ ... instance Ord Ordering -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Ord Int -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Ord Float -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Ord Double -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Ord Char -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Ord Bool -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Ord Integer -- Defined in ‘GHC.Num.Integer’ ...
Note how the Ord
declaration itself has a class constraint, i.e.,
Eq a =>
. This means any data type a
must already have its Eq
instance registered. Hence, Eq
is a sort of super-class, and yes,
type classes can build hierarchies of themselves.
Again, we see Ord
has a minimum requirement for order-ness, namely,
that we define the method[fn:36] (function) compare
… and then we
get all the other order-ness methods, (<)
… min
for free, as in
Haskell is smart enough to figure them out just based on what you gave
for compare
. Neat[fn:37].
:t compare
compare :: Ord a => a -> a -> Ordering
The function compare
is, like (==)
, a binary (two inputs)
operation that takes inputs of the same data type a
and returns
something of type Ordering
. So what is Ordering
? Let’s ask
:i Ordering
type Ordering :: * data Ordering = LT | EQ | GT -- Defined in ‘ghc-prim-0.7.0:GHC.Types’ instance Eq Ordering -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Monoid Ordering -- Defined in ‘GHC.Base’ instance Ord Ordering -- Defined in ‘ghc-prim-0.7.0:GHC.Classes’ instance Semigroup Ordering -- Defined in ‘GHC.Base’ instance Enum Ordering -- Defined in ‘GHC.Enum’ instance Show Ordering -- Defined in ‘GHC.Show’ instance Read Ordering -- Defined in ‘GHC.Read’ instance Bounded Ordering -- Defined in ‘GHC.Enum’
There’s a lot of information here. Note the data type declaration for
Ordering
is
... data Ordering = LT | EQ | GT ...
While the type Bool
had two data constructors True
and False
,
Ordering
has three — LT
, EQ
, and GT
. Now, let’s write code
to register an instance of Ord
for ~Color~[fn:38]
instance Ord Color where
compare Red Red = EQ
compare Red _ = GT
compare _ Red = LT
compare Yellow Yellow = EQ
compare Yellow _ = GT
compare _ Yellow = LT
compare Blue Blue = EQ
compare Blue _ = GT
compare _ Blue = LT
compare Green Green = EQ
Again, the Ord
type class required us to create our own compare
method, which painstakingly we did. Now we can compare two values
of Color
for order-ness with our basic Ord
comparison operators[fn:39],
>
, <
, and ==
,
((Blue < Green) || (Red == Red)) && (Yellow >= Blue)
True
(min Red Yellow) > (max Blue Green)
True
Now, how many possible combinations of these four colors would we have to make in order to test all possible cases? This is a topic the basics of which we’ll explore later. But here’s a taste of these future endeavors. Perhaps you noticed in LYAHFGG the talk about list comprehensions. These mimic set comprehensions fairly closely
\begin{align*}
& C = \{Red,Yellow,Blue,Green \}
& \{(x,y) \;|\; x ∈ C, \;y ∈ C \}
\end{align*}
And in Haskell
cset = [Red,Yellow,Blue,Green]
[ (x,y) | x <- cset, y <- cset]
[(Red,Red),(Red,Yellow),(Red,Blue),(Red,Green),(Yellow,Red),(Yellow,Yellow),(Yellow,Blue),(Yellow,Green),(Blue,Red),(Blue,Yellow),(Blue,Blue),(Blue,Green),(Green,Red),(Green,Yellow),(Green,Blue),(Green,Green)]
Let’s define another color type and simply rely on Haskell’s
deriving
to create instances for Eq
and Ord
data Color2 = Green2 | Blue2 | Yellow2 | Red2 deriving (Show,Read,Eq,Ord)
(Red2 == Red2 && Red2 == Green2)
False
For equal-ness deriving
simply made everything equal to itself and
not equal to other colors—just like we did before by hand. Then for
order-ness deriving
simply took the Color2
data constructors in
the order they were declared and ranked them in ascending order
((Green2 < Blue2) == (Blue2 < Yellow2)) == (Yellow2 < Red2)
True
We should explain one more thing at this point, namely, what that
first line of our :i
readouts says
:i Color
: type Color :: * : data Color = Red | Yellow | Blue | Green : -- Defined at omni1.1.hs:29:1 : instance [safe] Eq Color -- Defined at omni1.1.hs:30:10 : instance [safe] Ord Color -- Defined at omni1.1.hs:36:10 : instance [safe] Read Color -- Defined at omni1.1.hs:29:57 : instance [safe] Show Color -- Defined at omni1.1.hs:29:52
type Color :: *
is telling us that the type of the type Color
is
*
. Huh? So if the type of values Red
or Green
is Color
, the
type of Color
is it’s kind, here expressed by the symbol
*
. Color
has kind *
, which
says Color
is a type constructor of arity null, or a nullary
type constructor. So what does this mean? Let’s take
it apart…
Arity is something functions have.
In the second line, the data type declaration line, we see the left
side of the =
is Color
, which is the type constructor, and on
the right side are the data constructors or value constructors,
which are Red | Yellow | Blue | Green
, which are also referred to as
just the values of Color
. Again, the type of Red
is Color
, and
the type of Color
is *
, which is Haskell’s way of noting Color
has an arity of null. Which is just like Red
, Yellow
, Blue
, or Green
as possible values
instead of just Color
takes the null parameter and returns one of its
color data constructors as a value. Odd, but that’s Haskell.
So what would be an example of type with higher arity? What would a
data type look like that had a type constructor on the left side of
=
that did in fact take input like a function — and what does such
a thing give us? Consider this data type
data StreetShops a = Grant a | Lee a | Lincoln a deriving (Show,Read)
And its kind will look like this
:k StreetShops
StreetShops :: * -> *
The form * -> *
is Haskell’s way of saying the data type
StreetShops
indeed takes a single parameter like a function. And
that parameter a
is, like before, leveraging parametric
polymorphism, meaning a
can be any data type. This in turn makes
StreetShops
polymorphic, i.e., its values Grant
, Lee
, and
Lincoln
are able to take input of different data types. For example,
if we want our StreetShops
value Grant
to represent a list of all
the shops we visit on Grant Street[fn:42]
shopsOnGrant = Grant ["Tre Chic","Dollar Chasm","Gofer Burgers"]
shopsOnGrant
Grant ["Tre Chic","Dollar Chasm","Gofer Burgers"]
:t shopsOnGrant
shopsOnGrant :: StreetShops [String]
So each of the data constructors can take a parameter a
, which can
be anything. If we want a StreetShops
value to hold the average
number of visitors per day[fn:43]
visitorsOnGrant = Grant 1294
visitorsOnGrant
Grant 1294
:t visitorsOnGrant
visitorsOnGrant :: Num a => StreetShops a
Again, StreetShops
, by having kind or arity of one, is polymorphic
in its one parameter a
. And in reality there is no just
StreetShops
type; instead, StreetShops
has to be teamed up with
another type to be used. To be sure, StreetShops
is a contrived
example of the * -> *
kind. In reality there are probably better
ways of managing data about streets. But we’ll certainly use lots of
data types with higher arity, especially when we start going deeper
into some of the more math-derived features in Haskell. And so that’s
why we wanted to introduce what may seem pretty abstract at this
point[fn:44].
Did you notice that Num a =>
class constraint in the type details of
visitorsOnGrant
? When we created the variable visitorsOnGrant
we
gave the value constructor Grant
a number — but we didn’t say what
sort of number, Int
, Integer
, Float
… Haskell then inferred
that it was something numerical and constrained it with the type class
Num
…
Let’s take another look at the concept of class hierarchy. Remember
how we had Eq
equal-ness as a prerequisite for Ord
order-ness? We
needed equal-ness to then create order-ness. Consider this
:t 1
1 :: Num p => p
This is Haskell’s way of saying, Yes, this is some sort of literal
number you gave me. And all I can say back to you is it is a
number. And so the generic parameter p
has the class constraint that
whatever p
may be (here we provided 1
), it must be registered with
the type class Num
. So what is this Num
class?
:i Num
type Num :: * -> Constraint class Num a where (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-} -- Defined in ‘GHC.Num’ instance Num Word -- Defined in ‘GHC.Num’ instance Num Integer -- Defined in ‘GHC.Num’ instance Num Int -- Defined in ‘GHC.Num’ instance Num Float -- Defined in ‘GHC.Float’ instance Num Double -- Defined in ‘GHC.Float’ ...
Here we see number-ness defined through its methods. Whatever type
might want to be considered a number will need to have these
operations of addition, subtraction, multiplication, negation, etc.,
i.e., register an instance with the Num
class[fn:45].
Actually, Num
is a great place to start really seeing how
mathematical Haskell is. :i Num
gives us good look, but, as we
mentioned before, hackage.haskell.org, in this case GHC.Num.
What is this? Why is this mentioned? A simple starter explanation is
that we have here a set of guidelines for how addition and
multiplication must behave in order to have an instance of
Num
. Let’s continue…
So if multiplication is just a glorified sort of addition, and division is glorified subtraction[fn:46], then that makes addition and subtraction the basis of arithmetic, but they have a very fundamentally different behavior when used in the wild that we must, in turn, account for. Yes, addition puts things together and subtraction takes something away from another thing. But there’s further -ness to addition that subtraction doesn’t have, namely, order doesn’t seem to matter, whereas it does with subtraction (and of course division). Obviously it matters which number gets subtracted or divided by which, but not with addition and multiplication.
We mentioned binary operators before, which means whenever we add or
subtract we’re really taking just two numbers at a time[fn:47], i.e.,
the arity of the (+)
operator is two, or (+)
is a binary
operation. But again, why are we concerned with this?
Notice at GHC.Num it says
The Haskell Report defines no laws for
Num
. However, (+) and (*) are customarily expected to define a ring and …
The Haskell Report is a reference manual for how Haskell is put
together, often just showing us how certain pieces of the language are
coded under the hood. Typically, you see a Backus-Naur
description[fn:48], then some description/talk, maybe also examples
and “translations[fn:49].” GHC.Num says there is no particular
mention in the HR of these arithmetical laws, but (+)
and (*)
should define a ring. So here the lore is getting deep. What is
meant by a /ring/[fn:50]? Well, a ring is something from abstract
algebra, which you’ll see in a mathematics curriculum once you’re
beyond Frosh college math including calculus, differential equations,
and linear algebra. The basic idea is that a ring is a set
containing a set of numbers, along with key arithmetical operators
behaving in certain ways. In other words, a ring is a sort of like a
package which includes numbers and the operations that work on those
numbers[fn:51]. Then neatly bundled like this, we can do and say things about
them as a whole. But let’s leave it at that for now. We’ll soon
explore other similar “packagings” (semigroups, monoids, etc.) from
abstract algebra that are brought over for use in Haskell.
Continuing, if the so-called /fundamental laws of arithmetic/[fn:52] say
-
$a + b = b + a\quad$ (additive commutativity, AC) -
$ab = bc\quad$ (multiplicative commutativity, MC) -
$a + (b + c) = (a + b) + c\quad$ (additive associativity, AA) -
$a(bc) = (ab)c\quad$ (multiplicative associativity, MA) -
$a(b + c) = ab + ac\quad$ (distributive law, DL)
then in order to have “number-ness” a Num
instance for a number type
should have these behaviors when added or multiplied. But if we
compare, these five laws concerning addition and multiplication
aren’t exactly the same as those Haskell laws mentioned above from
GHC.Num. For one, Where’s #2, multiplicative commutativity, i.e., Num
class. More on
that later.
Num
is a super-class that is a prerequisite
“constraint” for anything number-like in Haskell. And so any type with
which we want to do basic arithmetic, i.e., to have number-ness, must
register an instance with the type class Num
, defining the minimum
set of methods in order to perform basic math operations.
This has been a brief, hurried introduction to ordinality — with a small detour to explore some of the LE of what numbers are vis-à-vis Haskell and computers. The notion of order is everywhere and cannot be taken for granted. And the idea of ordinality goes pretty deep in higher math. See this and this fire hose treatments as somewhere between RO and RFYI. (And see this for something we’ll eventually take a Haskell stab at.) Note especially the order axioms and the properties of ordered fields. In general, these treatments are upper-level/grad math — or, yes, upper-level comp-sci, depending on your future school’s program. Remember, comp-sci dips and weaves around and through higher math with little or no warning — a bit like physics routinely takes off into high math-land as well.
[fn:1] This is also on her GitHub repository.
[fn:2] See her popular layman’s book How To Bake
[fn:3] In many places in the world the typical American college Freshman-Sophomore math sequence of calculus, diff-eqs, and linear algebra is completed at the college-prep level. For example, Germany and Switzerland have college freshmen starting with Analysis.
[fn:4] parochial: … very limited or narrow in scope or outlook; provincial…
[fn:5] Make sure you’re attacking the LibreTexts series, e.g., one of the first three rabbit holes in the math section.
[fn:6] If machines were capable of conditioned learning, your car should be able to self-drive certain oft-travelled routes, e.g., from your home to the grocery store.
[fn:7] It was a long-revered feat of logical minimalism that all two-dimensional shapes in Elements could be produced with just a compass and a straightedge. Follow the Wikipedia link and note the animation of the construction of a hexagon. It wasn’t until the development of calculus and infinitesimal methods in the Renaissance that this compass-and-stick purity was set aside.
[fn:8] Later we’ll explore how Descartes united algebra and geometry.
[fn:9] Make sure you’re getting along with the Haskell rabbit hole materials — at the very least worked through half of LYAHFFG.
[fn:10] Two questions: Are repeating decimal numbers also rational? If
yes, then why can’t rational numbers represent all numbers? That is,
why do we need real and complex numbers? Programming challenge: Write
a function that takes a real decimal number and figures out its
fraction, e.g.
[fn:11] Professor Chandra connected this to Haskell by mentioning Haskell’s “point-free” programming … but didn’t elaborate.
[fn:12] But then measuring must be “quantified” by counting unit-wise what was measured; hence, everything comes back to counting. This will come up when we explore real numbers versus rational numbers.
[fn:13] Inside your head you automatically know how many and in what
order the numbers
[fn:14] We indicate the set’s cardinality by surrounding the symbol for a set with pipes ( **|** ), e.g., $|Sstars|\;$. This is not the same as absolute value, although they might be cousins.
[fn:15] Have a look at this Wikipedia discussion of cardinality. Later we will look into cardinal numbers, which is a deeper dive into set theory.
[fn:16] We’ll look at injective, surjective, and bijective when we delve into functions from a set theory perspective. Also, mathematical logic will introduce us to if and only if.
[fn:17] To excite your curiosity, isn’t
[fn:18] So it’s not really a “grocery list,” it’s a “grocery set”
since
[fn:19] Why is this? There is a LE to the definition
of a set union, namely,
[fn:20] Take a look at this Hackage page. It has an intro to Haskell’s
sets. Get in the habit of perusing Hackage whenever you’re using a
Haskell function or data type you don’t quite understand. Sometimes
you’ll have to just dive into the code, but sometimes there are
excellent intro docs like this. And yet there are also set operations
in Data.List
. Perhaps look into these two options.
[fn:21] Meanwhile, here’s a brain-teaser that goes to the heart of this set-list issue — especially in Haskell, Why are set union and intersection commutative and associative due to their lack of order?
[fn:22] See this brief discussion.
[fn:23] Typically, real numbers are represented geometrically by a
“number line” whereby interval notation allows for sections of this
line to be considered, e.g., closed interval
[fn:24] We will eventually investigate how costly in computer time and resources an operation like sorting is. Stay tuned.
[fn:25] A jigsaw puzzle can be seen as a sorting game based on shape, color, and patterns of the pieces.
[fn:26] For a quick introduction with examples go here in LYAHFGG.
[fn:27] Go ahead and check out the hackage.haskell.org entry for Eq
here. Note the properties (==)
should follow: reflexivity, symmetry,
transitivity, extentionality, and negation. We’ll dive into what this
all means when we look closer into the higher algebra of sets and
functions. Note all the built-in, “batteries-included” Eq
instances
for the various types. Some are rather exotic.
[fn:28] Quick LE question: Can functions be compared for equal-ness?
Here’s a direct quote from a prominent combinatorics text: When two
formulas enumerate the same set, then they must be equal. But not so
fast in the computer world. To say f x == g x
Haskell isn’t
logically set up to actually prove (demonstrate) and accept that f
and g
always give the same results given the same x
input. We
would literally have to test every possible x
, which is not
possible. Still, we’ll examine this idea a bit closer soon. It’s a
real big deal in numerical math.
[fn:29] Note {-# MINIMAL (==) | (/=) #-}
which is a directive
meaning we may choose to define either (==)
or (note the or
pipe | ) (/=)
, i.e., we don’t actually have to define both because
by defining one, the other will be automatically generated. Neat.
[fn:30] :t
is short for :type
.
[fn:31] ad hoc, from Latin to this, is something put together on the fly for one narrow, pressing, or special purpose. polymorphic, from Greek polus much, many, and morphism, having the shape, form, or structure, i.e., having many shapes.
[fn:32] As LYAHFGG says, Read
and Show
are also type classes to
which you may register your data type. Here we’ve used the deriving
keyword to let Haskell figure it out, i.e., we’re not defining
Read
and Show
ourselves; rather, we’re telling Haskell to
auto-generate and register these instances for us.
[fn:33] We’ll go into more detail about declaring our own data types
as we progress. But for now we’ll say Color
is a sum type, as
opposed to a product type. Sum types are patterned after the
addition principle which we’ll also go into later. Note, the pipes
[fn:34] …and this is an example of parametric polymorphism where
the parameter (aka type variable) a
can be any data type. In
Haskell, smaller-case letters such as a
, b
, c
, etc., are generic
parameter names and can indicate any data type. In (==) :: Eq a => a
-> a -> Bool
we see that two inputs of the same type a
are fed to
(==)
, which produces a Bool
output. Another example would be
myFunc :: a -> b -> a
. Here the type signature says the inputs don’t
have to be of the same type (although they could be), but no matter
what type the parameter b
is, the output will be of type a
.
[fn:35] For our Color
we’ve created our own equal-ness, although in
this example we could have let Haskell figure it out, i.e., ...|
Green deriving (Eq)
would have done the same thing. This was an easy
one. Haskell can’t always figure out the less obvious cases.
[fn:36] The habit of calling a set of functions associated with a Haskell type class methods might be a hold/spill-over from the world of object-oriented programming where an OOP class will have method functions attached to it. This is called encapsulation, i.e., a system for keeping things that belong together together. However, an OOP class and a Haskell type class are entirely different beasts. So let’s keep our head stuck in the Haskell particle accelerator for now…
[fn:37] In general, anytime your programming language starts writing code for you, it’s cool…
[fn:38] We make heavy use of the wildcard _
by which we mean any
variable can be in the _
position. For example compare Red _ = GT
means when we compare Red
to anything else, Red
will always be
greater than it. We also leveraged the order of these declarations,
i.e., by having compare Red _ = GT
at the very start, Red
versus
anything will be sorted out first. This is a conditional situation
implicitly, which we’ll use lots more.
[fn:39] Remember, math operators in Haskell are just a sort of
function. Which means (==) Red Red
is identical to Red ==
Red
. Haskell requires operators in the function (prefix) position
to be in parentheses, whereas in the infix (between) position they
can be naked operators. Notice min Red Yellow
that min
is in the
prefix position. Just put back-ticks around it to use it infix: Red `min` Yellow
.
[fn:40] This may be slightly confusing since in Algebra you probably
learned about constant functions expressed as, e.g.,
[fn:41] If a data constructor (also called value constructor) has a
nullary type constructor, as does Color
, then just like with Red
, Yellow
, Blue
,
Green
considered constants.
[fn:42] We’ll give data constructor Grant
a list of elements of type
String
, a Haskell String
being, in reality, a type synonym for
list of type Char
, which are individual Unicode characters.
λ> "Tre Chic" == ['T','r','e',' ','C','h','i','c']
\
True
[fn:43] Haskell has great powers of inferring, i.e., when we ask it
what type visitorsOnGrant
is, it deduces this from how we created
visitorsOnGrant
, namely: visitorsOnGrant :: Num a => StreetShops a
.
[fn:44] As another example of introducing an abstract subject out of the blue and way early, do you remember middle school math trying to show you commutativity, distributivity, and associativity? Well, they become important in higher math, especially in abstract algebra. Haskell has lots of higher algebra baked in, and yes, you’ll finally see a real-world application of commutativity, distributivity, and associativity soon!
[fn:45] Actually, our example :t 1
relies on the method
fromInteger
, but we’ll unpack that later. Even more actually, a
whole lot of complex magic is going on behind the scenes when dealing
with naked (literal) numbers like this. So yes, this is an example of
LE for doing numbers on computers with programming languages.
[fn:46] Multiplication is often phrased, e.g., “five fives,” i.e.,
five sets of five are to be considered (read added)
together. Likewise, division “divide
[fn:47] Think about it, even when you’ve got a whole list (vertical or
horizontal) of numbers to add, you’re really doing them two at a
time. One number becomes the addend and the other becomes the
augend, which is sort of a carrying-over holder to which the next
addend is added. So if we’re adding
[fn:48] More on Backus–Naur Form later. It’s used extensively to create a metasyntax for programming languages. Maybe rabbit-hole this Wikipedia treatment.
[fn:49] Try Haskell’s list comprehension. Don’t expect to understand what they’re saying just yet, but appreciate the magic and all the devilish details. And while you’re at it you might further appreciate just what a list comprehension is by looking at this Rosetta Code article. Again, YMMV on what you can grasp at this point, but maybe notice how Haskell is very Set-builder notation-friendly, while other languages just seem to be kludging something together. Again, maybe a bit too advanced, but check out Haskell’s entry for the Pythagorean triplets. Experiment with the code. A big part of learning to program is to read and experiment with code.
[fn:50] Have a look at this LibreText explanation but don’t try it without first getting through the basic set theory stuff in the assigned rabbit-hole.
[fn:51] Algebra in higher math is redefined to be a system that always “packages” a set of numbers with a set of useful operators/operations on those numbers. It’s good to start thinking this way in order to understand what Haskell and lots of computer science is doing and saying.
[fn:52] …taken from Richard Courant’s seminal What is Mathematics?