Haskell Road; Getting Started Part 1

3-d graph

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

Table 1: 15 factored into its primes
\(\quad\) 15 \(\quad\) \(\quad\) 2 \(\quad\) \(\quad\) 3 \(\quad\) \(\quad\) 5 \(\quad\)
    1 1
Table 2: 85 factored into its primes
\(\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]

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

License-disense