#
#
Itβs a Monday morning in late July and the von der Surwitz siblings meet in a reserved study room at the main university library to go over what their Introduction to Computer Science teacher, Professor Chandra, had discussed with them at the Novalis Tech Open House.
Checking Professor Chandraβs course syllabus on her website before, they had already made online purchases of the main text for the course, The Haskell Road to Logic, Maths and Programming by Kees Doets and Jan van Eijck. During the open house at the convention wing of the student center on the previous Saturday, they had sat down together at a table with the professor as she paged through the text and talked extemporaneously about what parts she would like to handle and how. She also showed them some code on her laptop. She invited them to email her with any questions.
The professor encouraged them to dive into to the logic and sets prereqs, alongside of the first few chapters of The Haskell Roadβ¦, as well as to get through as much Learn You a Haskellβ¦ as possible.
Ursula von der Surwitz has plugged in her laptop to the roomβs big 4K monitor and is scrolling through some of her personal notes.
ππ―π°π²π©π: So like
Professor Chandra said on Saturday, weβre going to rely heavily on
logic and set theory.
[murmurs of agreement and then silence] \
ππ―π°π²π©π: [continuing] It
seems a bit ominous. \
ππ΄π’: Out with the old
math, in with the new. \
[nods and agreement] \
ππ±π’:: But like she was
saying, comp-sci math is sort of a grab-bag of higher math, itβs bits
and pieces of what youβd be learning as a math major after all the
calculus and differential equations and linear algebra. \
[murmurs of agreement] \
ππ΄π’: Like the examples
she gave, you need set theory to properly define relations and
functions. \
[more agreement murmurs] \
ππ―π°π²π©π: Well, like
Vati[fn:1] said, his math degree really smoothed the way for his
chemical engineering studies. \
ππ΄π’: Get as much math
as you can as early as you can. That seems to be the plan everywhere
these days. \
[murmurs of agreement] \
ππ±π’: So Iβm psyched. \
[She looks around and receives nods] \
ππ±π’: [continuing] Iβm
psyched because β as she said β this course is totally
experimental and open-ended. Sheβs basically going to give us what
sheβd be giving her college freshman and sophomores in the CS
department. And because sheβs on sabbatical, sheβs giving us her total
undivided attention.\
ππ΄π’: Amazing. Itβs only
us, her daughter β and maybe three others who werenβt there and
might not take it after all. Wow.\
ππ±π’: Bottom line: Weβve
got a once-in-a-lifetime chance to really learn a ton about
comp-sci. Yeah, psyched! \
ππ΄π’: Now all we have to
do is hang on for dear life! \
[laughter] \
ππ―π°π²π©π: [paging through
Haskell Road⦠] Did anyone look at the first chapter? \
[affirmative nods] \
ππ―π°π²π©π: [continuing]
Wasnβt too bad β once I caught on to what he was saying. But I have
to say, the Learn You a Haskell⦠helped. \
[murmurs of agreement] \
ππ±π’: I think itβs cool
how the whole prime number test, the greatest common divisor all
blended together. \
ππ΄π’: But Iβm glad Iβd
seen the prime factorization way of figuring out unlike-denominator
math back in Kiel. If I can remember it. \
[affirmative murmurs] \
ππ±π’: Oh, by the way,
does anyone remember why the Greeks said, for example,
ππ―π°π²π©π: Well, I
remember prime factorization was based on the Fundamental Theorem of
Arithmetic from dear old Euclid. [bringing up the Wikipedia article
in both English and German, reading from the English page] /If a prime
ππ΄π’: [also ironically]
Oh sure, of course. \
[laughter] \
ππ―π°π²π©π: And hereβs the
proof. [half-mumbling the German pageβs proof] Itβs a contradiction
proof. Our favorite. \
[laughter] \
ππ΄π’: Oh, right. Thatβs
where you take off at top speed at a brick wall, smash into brick
wall, die β¦ and that proves you should have gone exactly the
opposite direction. \
[laughter] \
ππ±π’: Wasnβt it amazing
how Professor Chandra just put two fractions with huge unlike
denominators into the Racket command line[fn:3]. Did anybody besides
me install it? \
[affirmative murmurs] \
ππ―π°π²π©π: [bringing up
Racket in a terminal] Here, Iβll just try something. [typing] \
> (+ 1/15 1/85) 4/51
ππ±π’: I see you learned
the prefix way of doing Racket.
[Ursula nods.] \
ππ±π’: [going to the
whiteboard] Iβll put the prime factorization method down just to
refresh my memory. [writing] So
|
|
|
|
---|---|---|---|
1 | 1 |
|
|
|
|
|
|
|
|
---|---|---|---|---|---|---|---|
1 | 1 |
ππ±π’: [continuing] So we
take the unique primes in common between the two denominators β that
would be
ππ―π°π²π©π: [typing into
Racket] That gives us
\begin{align*} \frac{1}{15} &= \frac{x}{255} \[.5em] \frac{1}{85} &= \frac{y}{255} \end{align*}
ππ―π°π²π©π: [tabbing over
to her org-mode buffer] Here, let me write a little Haskell function
for that, a proverbial one-liner doing β Dreisatz? Whatβs
Dreisatz?
ππ±π’: Ah, literally
rule of three. \
ππ―π°π²π©π: [Ursula looks
it up on Wikipedia] Or just cross-multiplication. [typing into a
org-mode Babel source block] \
crossMult = \a b d -> ((a * d) / b) -- a/b = x/d ... solve for unknown
[running it with parameters]
crossMult 1 15 255
17.0
ππ΄π’: So youβre doing an
anonymous function[fn:4], right?
ππ―π°π²π©π: Right. Yeah,
pretty neat, huh? \
ππ΄π’: And youβre not
worrying about doing a type declaration. \
ππ―π°π²π©π: Not really. I
could. But Iβm just letting Haskell figure it out. Iβll get the type
with :t
[typing at the REPL]
:t crossMult
crossMult :: Double -> Double -> Double -> Double
ππ―π°π²π©π: [continuing] Actually, I could have done it this way [typing in new source block]
:{
crossMult2 :: Integer -> Integer -> Integer -> Integer
crossMult2 = \a b d -> ((a * d) `div` b)
:}
crossMult2 1 85 255
3
ππ±π’: So you
specifically declared the type signature for your crossMult2
. And it
you specifically made it just Integer
parameters producing an
Integer
answer. But then youβre using div
? Why then?
ππ―π°π²π©π: Watch
this. [typing into REPL] Iβll get some information on regular division
/
versus div
.
:t (/)
(/) :: Fractional a => a -> a -> a
:i (/)
type Fractional :: * -> Constraint class Num a => Fractional a where (/) :: a -> a -> a ... -- Defined in βGHC.Realβ infixl 7 /
:t div
div :: Integral a => a -> a -> a
ππ΄π’: Wow. Still getting
used to reading that stuff.
[laughter] \
ππ±π’: All right, so Iβm
assuming theyβre calling the one type class [writing on the board]
Fractional
because that means literally something thatβs numerical
and not a whole number. And then the other [writing] Integral
is
something related to an integer whole number. So basically, a type
class adds a property or trait to types. Itβs starting to get clearer
β maybe. \
[murmurs of agreement] \
ππ―π°π²π©π: Let me go to a
link on stackoverflow that talks about this[fn:5]. Getting
there⦠[displays post on monitor] Right. So the original post has
this and wonders why the error
(4 :: Integer) / 2
<interactive>:36:16: error: β’ No instance for (Fractional Integer) arising from a use of β/β β’ In the expression: (4 :: Integer) / 2 In an equation for βitβ: it = (4 :: Integer) / 2
ππ±π’: Iβm guessing itβs
saying you canβt do a regular fractional divide because youβve
specifically said this is an integer, right?
ππ―π°π²π©π: [getting up and
pointing on the monitor] Basically, yes. Thereβs a type class called
Fractional
and itβs saying that when we specifically make 4
an
Integer
this way [writing on the board] (4 :: Integer)
, that makes
it ineligible for doing regular fractional division. \
ππ΄π’: And thatβs because
the type and class system behind all this doesnβt allow it. \
ππ―π°π²π©π: Yes, as far as
I can tell. Did you get through the Learn You⦠part on type
classes[fn:6]? \
ππ΄π’: Yes, butβ¦ \
[laughter] \
ππ―π°π²π©π: I emailed the
professor about this and she said sheβd soon go over it in detail. \
[murmurs of acknowledgement] \
ππ―π°π²π©π: [continuing]
The bottom line β as someone is saying in the comments β is that
integer division not the same as fractional division. \
ππ΄π’: This type and
class stuff is why [pointing at the monitor] 4 / 2
works and this
(4 :: Integer) / 2
thing doesnβt. \
ππ―π°π²π©π: [typing into
the ghci REPL] Here, see?
4 / 2
2.0
ππ―π°π²π©π: It knows how to
pretend itβs not integer division. It goes ahead and gives you back a
decimal number.
[group studies the examples] \
ππ―π°π²π©π: In her email
the professor said we shouldnβt think of literal numbers like
[pointing] 4
and 2
as any sort of definite type just by itself
[typing into REPL]
:t 1
1 :: Num p => p
ππ―π°π²π©π: What this is
saying is that 1
is any number β until you commit to using it in
a certain way.
ππ±π’: So we need to use
div
if we specifically want integer division. \
ππ―π°π²π©π: Exactly [typing
into REPL]
(5 `div` 2)
2
ππ―π°π²π©π: See? Itβs rounding down and throwing away the remainder just like it should with whole numbers [typing into REPL]
5 / 2
2.5
:t (5 / 2)
(5 / 2) :: Fractional a => a
:t (5 `div` 2)
(5 `div` 2) :: Integral a => a
ππ΄π’: Good. I think Iβve
got it β in a shaky sort of way.
[silence] \
ππ―π°π²π©π: So back to the
lowest common denominator thing⦠\
[laughter] \
ππ―π°π²π©π: [continuing]
crossMult 1 85 255
3.0
ππ±π’: [continuing on the
board] That means
\begin{align*} \frac{17}{255} + \frac{3}{255} = \frac{20}{255} \end{align*}
ππ±π’: [continuing] So we
can reduce this by factoring out
\begin{align*}
\require{cancel}
\frac{\cancel{5} β
4}{\cancel{5} β
51}
\end{align*}
ππ΄π’: Wait. I think
weβre doing this wrong. We shouldnβt need to factor out the
[silence] \
ππ΄π’: [continuing] So
those tables, Ute, maybe we can just subtract one table from the other
and go with whateverβs left? \
ππ―π°π²π©π: [typing
calculations into the Racket command line] Not sure about that, dear
brother. My gut tells me β and my memory too β that no, itβs not
that simple. So, just for sake of argument, if they have a common
prime factor then letβs leave it out β¦ if theyβre the same
exponentially. So we had both denominators with the prime factor
$51\:$. Good. We drop it. But then what if they share a prime factor
but different exponentiations of it? What then? \
ππ΄π’: Explain,
please. \
ππ―π°π²π©π: [typing into
Racketβs REPL] So first, hereβs a contrived example where I know both
have $22$
> (+ 1/20 1/28) 3/35
ππ―π°π²π©π: [continuing] So
> (+ 1/88 1/80) 21/880
ππ―π°π²π©π: [continuing] So
Iβve put together a situation where
ππ΄π’: Yeah, I see. The
$24$ did have to go into it. And it was the
> (+ 5/20 11/28) 9/14 > (+ 1/20 11/28) 31/70 > (+ 3/20 13/28) 43/70
[laughter]
ππ΄π’: Oops. My bad. That
was a complete fluke factoring out the $22$ and just calling the
lowest common denominator
crossMult 5 20 140
35.0
crossMult 11 28 140
55.0
ππ±π’: [writing on the board] So this is what we have
\begin{align*} \frac{35}{140} + \frac{55}{140} = \frac{90}{140} = \frac{9}{14} \end{align*}
and no
ππ΄π’: Got it. \
[silence as they all read further into the article] \
ππ―π°π²π©π: So yes, now I
think we should try what theyβre saying where you list out multiples
of the two denominators until you find the first common
multiple. Letβs try it as a Haskell program. \
[agreement murmurs] \
ππ―π°π²π©π: [making a new
org-mode source block] Okay, so weβre basically talking about an
arithmetic series where a sequence of numbers increases or decreases
at a fixed amount. In this case the series will increase by the amount
of the denominators at each step. So let me do this [typing]
:{
myLCM dnom1 dnom2
= take 1 [((*dnom1)x) | x <- [1..200],
y <- [1..200],
((*dnom1)x == (*dnom2)y) ]
:}
ππ―π°π²π©π: [continuing]
Now for denominators
myLCM 15 85
[255]
ππ―π°π²π©π: [continuing] So
it works. But I donβt think itβs good for really big
denominators. This is strictly proof of concept.
[Uwe and Ute study the code] \
ππ΄π’: Again, wow. But
you lost me on the (*dnom1)
and (*dnom2)
. Whatβs going on there?
\
ππ―π°π²π©π: Thatβs what
they call a section. I got that the other day from /A Gentle
Introduction to Haskell/[fn:10]. Basically, *dnom1
is made into a
function that takes whatever dnom1
is and applies it just as if it
were a function to the x
. So (*dnom1)x
is the same as just x *
dnom1
. Hereβs an example [typing into the ghci REPL]
(*2)5 == (2*)5
True
ππ―π°π²π©π: [continuing]
Since multiplication is commutative, the *
sign can be in front of
the multiplicand or behind it. But thatβs not always the case. For
example [typing]
(2^)3
8
(^2)3
9
ππ―π°π²π©π: [continuing] So
the second one flips the
myTimesTwo = (*2)
myTimesTwo 5
10
ππ΄π’: Okay, I get
it. Impressive.
ππ―π°π²π©π: So hereβs one
more cool thing about sections. Look at this and try to guess what it
does [typing into a source block]
myListOfAddFuncs = map (+) [1,2,3]
ππ±π’: Aaah, not
sure. Give us a hint.
ππ―π°π²π©π: Right, so
according to The Gentle Introductionβ¦ weβve got [writing on the
board]
(+) = \x y -> x + y
ππ±π’: So with sections
the operator and whatever variable you include has to be in
parentheses, right?
ππ―π°π²π©π: Yes. Weβre
packaging up a common operator like plus or times, and maybe a
variable, to work as a function. \
ππ΄π’: /Luft von anderem
Planeten/[fn:11] \
[laughter] \
ππ΄π’: [continuing] No,
really, I barely understand that anonymous stuff, but I hear you
saying myListOfAddFuncs
could have been written with an anonymous
function [writes on the board]
myLOAF = map (\x y -> x + y) [1,2,3]
ππ΄π’: [continuing] And Iβm guessing that this creates a list of functions like [writing]
[(1+),(2+),(3+)]
ππ―π°π²π©π: So letβs just test it [typing]
map (myListOfAddFuncs !! 2) [1,2,3]
[4,5,6]
ππ±π’: Again, I just
barely understand all these components. I sort of understand map
. I
sort of understand what youβre doing with sections, as theyβre
called. I sort of understand what Brother just said. So Iβm going to
guess that you have the list of functions in the form of sections, and
you just told it to apply this list of functions to the list
[1,2,3]
.
ππ―π°π²π©π: Not
exactly. Iβm still trying to figure that one out. No, what Iβm doing
is using the Haskell index operator !!
to say βgive me the third
element,β which is the function (3+)
. Even though it says 2
, the
index starts at 0
, so the third function in myListOfAddFuncs
is
(3+)
and map
then applies it across [1,2,3]
giving [4,5,6]
since it adds 3
to each element of the input list. See?
Verstanden? \
[nods and murmurs of agreement] \
ππ΄π’: So back to the β
list comprehension? Is that what itβs called? \
ππ―π°π²π©π: Right
[scrolling back]
myLCM dnom1 dnom2 = take 1 [((*dnom1)x) | x <- [1..200], y <- [1..200], ((*dnom1)x == (*dnom2)y) ]
myLCM 15 85
[255]
ππ―π°π²π©π: So this is a
/list comprehension/[fn:12], and a list comprehension is just the
Haskell version of a set comprehension or /set-builder
notation/[fn:13] from set theory. And itβs going through the multiples
of
15 30 45 60 75 90 105 120 ... 240 255 85 170 225
ππ―π°π²π©π: [continuing]
making pairs of all these possible combinations until thereβs a pair
that match, that share the same multiple, which is
ππ±π’: So letβs change it
a bit. What happens if you donβt have the filter that selects just
the equal pairs and you donβt take just the first one like you did
with that take 1
? \
ππ―π°π²π©π: Right, I know
what you mean. Here it is [typing]. Hereβs all the pairs of the two
intervals in all possible combinations β this time only the first
myLCM2 dnom1 dnom2 = [((*dnom1)x,(*dnom2)y) | x <- [1..15], y <- [1..15] ]
myLCM2 15 85
[(15,85),(15,170),(15,255),(15,340),(15,425),(15,510),(15,595),(15,680), (15,765),(15,850),(15,935),(15,1020),(15,1105),(15,1190),(15,1275), (30,85),(30,170),(30,255),(30,340),(30,425),(30,510),(30,595),(30,680), (30,765),(30,850),(30,935),(30,1020),(30,1105),(30,1190),(30,1275), (45,85),(45,170),(45,255),(45,340),(45,425),(45,510),(45,595),(45,680), (45,765),(45,850),(45,935),(45,1020),(45,1105),(45,1190),(45,1275), (60,85),(60,170),(60,255),(60,340),(60,425),(60,510),(60,595),(60,680), (60,765),(60,850),(60,935),(60,1020),(60,1105),(60,1190),(60,1275), (75,85),(75,170),(75,255),(75,340),(75,425),(75,510),(75,595),(75,680), (75,765),(75,850),(75,935),(75,1020),(75,1105),(75,1190),(75,1275), (90,85),(90,170),(90,255),(90,340),(90,425),(90,510),(90,595),(90,680), (90,765),(90,850),(90,935),(90,1020),(90,1105),(90,1190),(90,1275), (105,85),(105,170),(105,255),(105,340),(105,425),(105,510),(105,595), (105,680),(105,765),(105,850),(105,935),(105,1020),(105,1105), (105,1190),(105,1275),(120,85),(120,170),(120,255),(120,340), (120,425),(120,510),(120,595),(120,680),(120,765),(120,850), (120,935),(120,1020),(120,1105),(120,1190),(120,1275),(135,85), (135,170),(135,255),(135,340),(135,425),(135,510),(135,595),(135,680), (135,765),(135,850),(135,935),(135,1020),(135,1105),(135,1190), (135,1275),(150,85),(150,170),(150,255),(150,340),(150,425),(150,510), (150,595),(150,680),(150,765),(150,850),(150,935),(150,1020),(150,1105), (150,1190),(150,1275),(165,85),(165,170),(165,255),(165,340), (165,425),(165,510),(165,595),(165,680),(165,765),(165,850), (165,935),(165,1020),(165,1105),(165,1190),(165,1275),(180,85), (180,170),(180,255),(180,340),(180,425),(180,510),(180,595),(180,680), (180,765),(180,850),(180,935),(180,1020),(180,1105),(180,1190),(180,1275), (195,85),(195,170),(195,255),(195,340),(195,425),(195,510),(195,595), (195,680),(195,765),(195,850),(195,935),(195,1020),(195,1105),(195,1190), (195,1275),(210,85),(210,170),(210,255),(210,340),(210,425),(210,510), (210,595),(210,680),(210,765),(210,850),(210,935),(210,1020),(210,1105), (210,1190),(210,1275),(225,85),(225,170),(225,255),(225,340),(225,425), (225,510),(225,595),(225,680),(225,765),(225,850),(225,935),(225,1020), (225,1105),(225,1190),(225,1275)]
ππ΄π’: Right. Wow. So
this is literally taking all combinations of the multiples of (255,255)
, which
is the very first common multiples match, which makes it the lowest
match. But then your original code is filtering out all the
unnecessary combinations of the multiples.
ππ΄π’: So with the
original code, the x <- [1..200]
and the y <- [1..200]
simply
youβre going through (*dnom1)x == (*dnom2)y
youβre
keeping only the times you get a hit, that is,
[fn:1] German for dad, papa.
[fn:2] Take a break and check out this theory treatment.
[fn:3] Professor Chandra has demonstrated at the Racket command line
how rationals could be directly added, e.g.,
> (+ 1/32 1/943720)
\
and get back \
117969/3774880
[fn:4] Check out anonymous or lambda functions here.
[fn:5] See Why it is impossible to divide Integer number in Haskell?
[fn:6] See Typeclasses 101 in Learn You a Haskellβ¦.
[fn:7] German for mom, mama.
[fn:8] See the article here.
[fn:9] β¦In arithmetic and number
theory, the least common multiple, lowest common multiple, or
smallest common multiple of two integers
[fn:10] Available here. The page dealing with sections is here in 3.2.1. Also The wiki.haskell.org has a page on sections as well.
[fn:11] A famous line from a Stefan Georg poem: Ich fΓΌhle Luft von anderem Planeten or I feel (the) air of (the other) another planet.
[fn:12] See this from LYAHFGG.
[fn:13] See Wikipediaβs Set-builder notation