Haskell Road; Getting Started Part 1
Getting started part 1
Monday at the library part 1
Fractions
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
Vati1
German for dad, papa.
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, \(7\) measures
\(35\;\)?
ππ―π°π²π©π: Wasn’t that
because a rod or measuring tape that’s \(7\) units long can then measure
out a length of \(35\) perfectly?
[murmurs of agreement]
ππ―π°π²π©π: 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
\(p\) divides the product \(a \cdot b\), then \(p\) divides either \(a\) or \(b\) or
both. And that really means [reading on] Every positive integer \(n >
1\) can be represented in exactly one way as a product of prime
powers. Got that? [pulling an ironic face] Do you see how the first
and second statements are equivalent?
ππ΄π’: [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 line2
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
. 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 \(15\) is multiplying prime numbers \(3\)
and \(5\), and \(85\) is multiplying primes \(5\) and \(17\) together. Now
we’ve got the primes. I’ll make a table of them
\(\quad\) 15 \(\quad\) | \(\quad\) 2 \(\quad\) | \(\quad\) 3 \(\quad\) | \(\quad\) 5 \(\quad\) |
---|---|---|---|
1 | 1 |
\(\quad\) 85 \(\quad\) | \(\;\) 2 \(\;\) | \(\;\) 3 \(\;\) | \(\;\) 5 \(\;\) | \(\;\) 7 \(\;\) | \(\;\) 11 \(\;\) | \(\;\) 13 \(\;\) | \(\;\) 17 \(\;\) |
---|---|---|---|---|---|---|---|
1 | 1 |
ππ±π’: [continuing] So we
take the unique primes in common between the two denominators — that
would be \(3\;\), \(5\;\), and \(17\:\) and multiply them together to
get…
ππ―π°π²π©π: [typing into
Racket] That gives us \(255\;\).
ππ±π’: So now \(255\) will
be the common denominator. But we have to calculate the ratios
[writing]
ππ―π°π²π©π: [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
<interactive>:10:1-9: error: Variable not in scope: crossMult :: t0 -> t1 -> t2 -> t
ππ΄π’: So you’re doing an
anonymous function3
Check out anonymous or lambda functions here.
, 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
<interactive>:1:1-9: error: Variable not in scope: crossMult
ππ―π°π²π©π: [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 :: Integer -> Integer -> Integer -> Integer crossMult2 = \a b d -> ((a * d) `div` b) :}
crossMult2 1 85 255
<interactive>:14:1-10: error: Variable not in scope: crossMult2 :: t0 -> t1 -> t2 -> t
ππ±π’: 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 this4
See Why it is impossible to divide Integer number in Haskell?
. Getting
there… [displays post on monitor] Right. So the original post has
this and wonders why the error
(4 :: Integer) / 2
In an equation for βitβ: it = (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
classes5
See Typeclasses 101 in Learn You a Haskell….
?
ππ΄π’: 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
<interactive>:24:1-5: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βDoubleβ in the following constraints (Show a0) arising from a use of βprintβ at <interactive>:24:1-5 (Fractional a0) arising from a use of βitβ at <interactive>:24:1-5 β’ In a stmt of an interactive GHCi command: print it 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 a => a
ππ―π°π²π©π: 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)
<interactive>:28:1-11: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Show a0) arising from a use of βprintβ at <interactive>:28:1-11 (Integral a0) arising from a use of βitβ at <interactive>:28:1-11 β’ In a stmt of an interactive GHCi command: print it 2
ππ―π°π²π©π: See? It’s rounding down and throwing away the remainder just like it should with whole numbers [typing into REPL]
5 / 2
<interactive>:30:1-5: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βDoubleβ in the following constraints (Show a0) arising from a use of βprintβ at <interactive>:30:1-5 (Fractional a0) arising from a use of βitβ at <interactive>:30:1-5 β’ In a stmt of an interactive GHCi command: print it 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]
\(17\:\) is the amount the numerator of the original \(1/15\) has to be
multiplied by to be equivalent using the new denominator \(255\:\). So
now \(1/15\) is equivalent to \(17/255\;\). Next up, for \(1/85\:\) we’ve
got
crossMult 1 85 255
<interactive>:36:1-9: error: Variable not in scope: crossMult :: t0 -> t1 -> t2 -> t
ππ±π’: [continuing on the board] That means \(1/85\:\) is equivalent to \(3/255\:\), and then adding gives us
\begin{align*} \frac{17}{255} + \frac{3}{255} = \frac{20}{255} \end{align*}ππ±π’: [continuing] So we can reduce this by factoring out \(5\)
\begin{align*} \require{cancel} \frac{\cancel{5} \cdot 4}{\cancel{5} \cdot 51} \\ \end{align*}
ππ΄π’: Wait. I think
we’re doing this wrong. We shouldn’t need to factor out the \(5\;\),
right? So the common denominator should have been just the prime \(3\)
times the prime \(17\) because the prime \(5\) should have been left
out. Both \(15\) and \(85\) just have one \(5\) each as a factor. So if they
both have \(5\) let’s just leave it out.
[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
\(5^{1}\:\). 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 \(2^{2}\)
> (+ 1/20 1/28) 3/35
ππ―π°π²π©π: [continuing] So \(20\) is \(2^{2}\) times \(5\:\), and \(28\) is that same \(2^{2}\) times \(7\;\). So yes, we’ll get rid of the \(2^{2}\) — they’re the same prime factor raised to the same exponent — and we just multiply the \(5\) and the \(7\) to get \(35\:\), which is the denominator \(35\;\). And the answer Racket gives us, \(3/35\;\), is in simplest form, so we know it’s right. But, let me try an example where there’s a prime to a power in one denominator and the same prime to a higher power in the other [she does calculations on a sheet of paper, then types into the command line]
> (+ 1/88 1/80) 21/880
ππ―π°π²π©π: [continuing] So
I’ve put together a situation where \(80\) is \(2^{4}\) times \(5\;\), and \(88\)
is \(2^{3}\) times \(11\;\). And here we see \(880\:\) is the smallest
denominator possible. And \(21/880\;\) is in simplest form, so there’s
no factoring out to get it into simpler terms. And yes, \(2^{4}\) times \(5\)
times \(11\) went into making \(880\;\).
ππ΄π’: Yeah, I see. The
\(2^{4}\) did have to go into it. And it was the \(2\) with the higher
exponent. So when I said subtract one table from the other I guess I
was implying we could do exponent subtraction, like \(2^{4-3}\:\), get just
the \(2\) and then make up a denominator out of \(2 \cdot 5 \cdot 11\) which would
not have worked. Hey, should we try to write a Haskell function to
check all this?
ππ±π’: Hold that thought,
not yet. And I say that because The Haskell Road… has a way we
should learn. I looked at it.
ππ―π°π²π©π: Well, I asked
Mutti6
German for mom, mama.
about this — and she said we should look into the least
common multiple, because that’s what this lowest common denominator
issue is all about. And yes, I knew that but had forgotten. So here’s
the Wikipedia article on it7
See the article here.
. [displays page on monitor]
ππ΄π’: [mumbling the
first sentence8
…In arithmetic and number
theory, the least common multiple, lowest common multiple, or
smallest common multiple of two integers \(a\) and \(b\:\), usually
denoted by \(lcm(a, b)\:\), is the smallest positive integer that is
divisible by both \(a\) and \(b\;\)…
] You’re right. I knew this but had lost track of
it.
ππ±π’: Which means we
don’t want to kick out any primes with the same exponent just
because the denominators share them.
[embarrassed laughter]
ππ―π°π²π©π: [typing into
the Racket REPL] So trying \(20\) and \(28\) as denominators again, we
thought \(35\) was the common denominator for all situations. But watch,
I’ll put in some different numerators
> (+ 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 \(2^{2}\) and just calling the
lowest common denominator \(5\) times \(7\:\). So the real lowest common
multiple, the smallest number that both \(20\) and \(28\) evenly divide
into is, in fact, \(2^{2}\) times \(5\) times \(7\;\), which is \(140\;\).
ππ―π°π²π©π: Now, let’s get
the numerators [typing]
crossMult 5 20 140
<interactive>:38:1-9: error: Variable not in scope: crossMult :: t0 -> t1 -> t2 -> t
crossMult 11 28 140
<interactive>:40:1-9: error: Variable not in scope: crossMult :: t0 -> t1 -> t2 -> t
ππ±π’: [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 \(35\) in sight.
ππ΄π’: 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 \(15\) and \(85\) like before
myLCM 15 85
<interactive>:42:1-5: error: Variable not in scope: myLCM :: t0 -> t1 -> t
ππ―π°π²π©π: [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 Haskell9
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.
. 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
<interactive>:44:7-8: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Eq a0) arising from a use of β==β at <interactive>:44:7-8 (Num a0) arising from a use of β*β at <interactive>:44:2 β’ In the expression: (* 2) 5 == (2 *) 5 In an equation for βitβ: it = (* 2) 5 == (2 *) 5 <interactive>:44:7-8: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Eq a0) arising from a use of β==β at <interactive>:44:7-8 (Num a0) arising from a use of β*β at <interactive>:44:2 β’ In the expression: (* 2) 5 == (2 *) 5 In an equation for βitβ: it = (* 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
<interactive>:46:1-5: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Show a0) arising from a use of βprintβ at <interactive>:46:1-5 (Num a0) arising from a use of βitβ at <interactive>:46:1-5 β’ In a stmt of an interactive GHCi command: print it <interactive>:46:3: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Integral a0) arising from a use of β^β at <interactive>:46:3 (Num a0) arising from the literal β3β at <interactive>:46:5 β’ In the expression: (2 ^) 3 In an equation for βitβ: it = (2 ^) 3 <interactive>:46:3: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Integral a0) arising from a use of β^β at <interactive>:46:3 (Num a0) arising from the literal β3β at <interactive>:46:5 β’ In the expression: (2 ^) 3 In an equation for βitβ: it = (2 ^) 3 8
(^2)3
<interactive>:48:1-5: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Show a0) arising from a use of βprintβ at <interactive>:48:1-5 (Num a0) arising from a use of βitβ at <interactive>:48:1-5 β’ In a stmt of an interactive GHCi command: print it <interactive>:48:2: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Integral a0) arising from a use of β^β at <interactive>:48:2 (Num a0) arising from the literal β2β at <interactive>:48:3 β’ In the expression: (^) In the expression: (^ 2) 3 In an equation for βitβ: it = (^ 2) 3 <interactive>:48:2: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Integral a0) arising from a use of β^β at <interactive>:48:2 (Num a0) arising from the literal β2β at <interactive>:48:3 β’ In the expression: (^) In the expression: (^ 2) 3 In an equation for βitβ: it = (^ 2) 3 9
ππ―π°π²π©π: [continuing] So the second one flips the \(3\) to the other side. Not to get too far down this rabbit hole, I could write something like this [typing]
myTimesTwo = (*2)
myTimesTwo 5
<interactive>:52:1-12: warning: [-Wtype-defaults] β’ Defaulting the type variable βa0β to type βIntegerβ in the following constraints (Show a0) arising from a use of βprintβ at <interactive>:52:1-12 (Num a0) arising from a use of βitβ at <interactive>:52:1-12 β’ In a stmt of an interactive GHCi command: print it 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
Planeten10
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.
[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]
<interactive>:58:1-35: warning: [-Wtype-defaults] β’ Defaulting the type variable βb0β to type βIntegerβ in the following constraints (Show b0) arising from a use of βprintβ at <interactive>:58:1-35 (Num b0) arising from a use of βitβ at <interactive>:58:1-35 β’ In a stmt of an interactive GHCi command: print it [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?
[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
<interactive>:60:1-5: error: Variable not in scope: myLCM :: t0 -> t1 -> t
ππ―π°π²π©π: So this is a list comprehension11 See this from LYAHFGG. , and a list comprehension is just the Haskell version of a set comprehension or set-builder notation12 See Wikipedia’s Set-builder notation from set theory. And it’s going through the multiples of \(15\) and \(85\), one after the other like this [writing on the board]
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 \(255\;\).
ππ±π’: 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
\(15\) multiples of \(15\) and \(85\:\) so it doesn’t get too big
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 \(15\) and
\(85\;\), and there towards the end you can see the (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 \(200\) of the arithmetic progression for both the
\(15\) and the \(85\;\), and you’re creating all the different combination
pairs of these multiples of \(15\) and \(85\;\), the first time the
multiples are the same, you’ve got a winner. This time it was when it
returned \((225,225)\;\). So you just take the first parameter, which is
\(15\) times all these different combinations of multiples of \(15\) and
\(85\;\), and with that qualifier (*dnom1)x == (*dnom2)y
you’re
keeping only the times you get a hit, that is, \(255\) equals \(255\;\)
— and then you take the first one of that list.
[silence while studying the code]
ππ±π’: [dramatically] And
now, dear siblings, we can honestly say that we can solve
unlike-denominator fractions.
[laughter] ππ΄π’: I have
to say, when we have to pull math out of our heads and put on a
computer, it’s, yeah, involved, to say the least.
ππ΄π’: But I can see the
day when we’re good enough at coding to just immediately shake
something out of our sleave.
ππ΄π’: But it won’t be
easy getting there.
[murmurs of agreement]
ππ―π°π²π©π: Take a break?
[agreement]