Notes from Learning Haskell

2018-07-17

It feels like there are so many great functions available from Haskell and they seem to make a great deal of sense

Like succ 1 gives 2, the successor to 1

I'm used to a method like this being called add, but that feels like a function which would mutate and succ does not


Composability feels great

Reading about max (it makes sense that there's a max function, but I hadn't needed it yet), I wondered if I could adapt it for finding the largest value from a list of numbers

And it went exactly as I'd hoped and thought it might!

Where max 1 2 gives 2, foldr max 0 [1,2,10,3,4] gives 10

There's likely a better way to do this (and this example returns 0 if no larger number is found and that's not great), but it's nice that such a thing is so simple


Composing functions is fun

add x y = x + y is the same as add = (+)

They both take two arguments and return their sum

Except they aren't the same because the former is a function and the latter is a definition

one = 1
add one one
// 2

I'm not yet sure how this compares to variables in other languages, but I have some hunches


Trailing apostrophes in function names is a convention to denote strictness (non-laziness) or a slightly modified version of the non apostrophe'd name

I don't know what strictness looks like yet


Lists ([1,2,3]) are homogenous data structures (no mixing types)


++ is used for concatenation

[1] ++ [2] gives [1,2]

And strings are just lists of characters so

"Hello," ++ " " ++ "World!" gives Hello, World!

foldr (++) [] ["Hello,", " ", "World!"] also works (I'm going to keep coming back to foldr, aren't I)

++ walks through the entire first list before appending. Yikes


:, known as the cons operator, adds a thing to the beginning of a list and is apparently much more efficient

1:2:3:[] == [1,2,3]

foldr (:) "" ['o', 'k', 'a', 'y'] == "okay"


Getting an element at an index is accomplished with !!

['o','k','a','y'] !! 0 == 'o'

Alright I'm kind of shook here...

head' = (!! 0) works? You can pass arguments in upfront? Dang

2018-07-24

Using comparators on lists is interesting

[1, 1] > [1] -- because 1 and 1 are the same and then 1 is greater than nothing
[1, 3] > [1, 2] -- because 1 and 1 are the same and then 3 is greater than 2
[0] > [] -- because 0 is greater than nothing

And there are many functions for working with lists

head [0, 1, 2, 3, 4]
-- 0
tail [0, 1, 2, 3, 4]
-- [1, 2, 3, 4]
init [0, 1, 2, 3, 4]
-- [0, 1, 2, 3]
last [0, 1, 2, 3, 4]
-- 4

head []
-- Exception
length [0, 1, 2, 3, 4]
-- 5
null []
-- True
null [1]
-- False
reverse [0, 1, 2, 3, 4]
-- [4, 3, 2, 1, 0]
take 2 [0, 1, 2, 3, 4]
-- [0, 1]
take 0 [0, 1, 2, 3, 4]
-- []
take 10 [0, 1, 2, 3, 4]
-- [0, 1, 2, 3, 4]

drop 2 [0, 1, 2, 3, 4]
-- [2, 3, 4]
drop 0 [0, 1, 2, 3, 4]
-- [0, 1, 2, 3, 4]
drop 10 [0, 1, 2, 3, 4]
-- []
maximum [0, 1, 2, 3, 4]
-- 4
minimum [0, 1, 2, 3, 4]
-- 0
sum [0, 1, 2, 3, 4]
-- 10
product [0, 1, 2, 3, 4]
-- 0
0 `elem` [0, 1, 2, 3, 4]
-- True
5 `elem` [0, 1, 2, 3, 4]
-- False

Ranges seem very friendly

[0..4]
-- [0, 1, 2, 3, 4]
['a'..'e']
-- "abcde"
['F'..'J']
-- "FGHIJ"
[0, 3..9]
-- [0, 3, 6, 9]
['A', 'C'.. 'Z']
-- "ACEGIKMOQSUWY"

Different ways to get the first ten multiples of 3

[3, 6..3*10] == take 10 [3,6..]
-- True

And there are methods for generating lists...

take 5 (cycle "LO")
-- "LOLOL"

take 5 (repeat 'A') -- repeat 'A' creates an infinite list
-- "AAAAA"

replicate 5 'A' -- replicate 5 'A' creates a list of five As
-- "AAAAA"

2018-07-31

List comprehensions are amazing and I don't appreciate having had to live without them for so long

[x*2 | x <- [1..3]]
-- [2,4,6]

Here's an example with a predicate which filters out even numbers

[x | x <- [1..10], odd x]

You can also draw from multiple lists getting every possible combination consisting of one value from each list

let nouns = ["coffee", "notebook", "headphones"]
let adjectives = ["iced", "fresh", "loud"]
[a ++ " " ++ n | a <- adjectives, n <- nouns]
-- ["iced coffee","iced notebook","iced headphones","fresh coffee","fresh notebook","fresh headphones","loud coffee","loud notebook","loud headphones"]

And values from a list fed into a list comprehension don't need to be used

In this example, length' takes a list and converts each of its members to 1 and then adds them together producing the length of the list

length' xs = sum [1 | _ <- xs]

Nested list comprehensions are a thing too!

let xxs = [[1..3], [4..6], [7..9]]
[ [ x | x <- xs, even x ] | xs <- xxs]
-- [[2],[4,6],[8]]

Tuples are fixed size and used for storing heterogenous elements as a single value

(1, 'A')

A tuple of size two is called a pair and a tuple of size three is called a triple

Tuples of a type must be consistent

-- This won't work because the first value in the first pair is a list of characters while the first value in the second pair is a list of numbers
[("foo", 1), ([1,2,3], 2)]

There are some handy functions for working specifically with pairs like fst and snd

fst (1,'A') -- 1
snd (1,'A') -- 'A'

zip takes one item at a time from two lists to create tuples until one of the lists is exhausted

zip [1..10] [1..]
-- [(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10)]

I'm taking this next exercise on finding right triangles verbatim from Learn You a Haskell

-- First we're generating all the triples with integers less than or equal to i
-- 10
[ (a,b,c) | c <- [1..10], a <- [1..10], b <- [1..10]]

-- Then we're adding a predicate to check if the triangles are right triangles
-- by checking that `a^2 + b^2 == c^2`
-- We're also only checking triples where a is less than c because the 
-- hypotenuse of a right triangle is always its longest side and we're only
-- checking triples where b is less than a because otherwise we'd end up with
-- duplicated triples
[ (a,b,c) | c <- [1..10], a <- [1..c], b <- [1..a], a^2 + b^2 == c^2]

-- Finally we're adding another predicate to ensure that the sum of the length 
-- of the sides is 24 (this is an arbitrary constraint)
[ (a,b,c) | c <- [1..10], a <- [1..c], b <- [1..a], a^2 + b^2 == c^2, a+b+c == 24]

2018-08-07

In Haskell, every expression's type is known at compile time

Unlike some other languages, Haskell has type inference, so it knows a number is a number without being told it's a number

:t follow by an expression, like :t ('A', 1), provides the type of that expression

I've been using it a lot as I learn and it's great but also confusing when it says a thing I do understand is a type I don't understand making me suspect that I don't truly understand the thing at all


:: is used to describe what type a thing has in code

addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z

Int is an integer with bounds Integer is an integer without bounds, so it can be used for larger numbers, but it's less efficient than Int

Float is a floating-point number with single precision Double is a floating-point number with double precision (more precise, less performant)

Bool is True or False

Char is a unicode character

Tuples have theoretically infinite type possibilities


Haskell supports polymorphic functions which use type variables such as the built-in head

:t head
head :: [a] -> a

Haskell has Type Classes like Eq, which means the thing can be called with == and /=, and Show, which means the thing can be called with show

show converts a thing to a string, read, implemented on members of the Read class, converts a string into a thing

show 1
-- "1"
read "1" + 1
-- 2

2018-08-14

When using read to convert a string into another type, type annotations are helpful

read "1.0" -- *** Exception: Prelude.read: no parse
read "1.0" :: Float -- 1.0 

read "('A', 0)" :: (Char, Int) -- ('A',0)

(), Bool, Char, Ordering, Int, Integer, Float, and Double are all members of the Enum type class

This means that they are all enumerable, so each instance can be called with succ and pred and they can all compose ranges


The Bounded type class have an upper and lower bound

maxBound :: Int -- 9223372036854775807

Tuples whose components are all instances of Bounded are also considered instances of Bounded

maxBound :: (Int, Char) -- (9223372036854775807,'\\1114111')

Instances of Num act like numbers and are all also members of Show and Eq

Whole numbers are polymorphic constants

:t 100 -- 100 :: Num p => p

-- so

100 :: Float -- 100.0
-- and
(100 :: Double) * (100 :: Double) -- 10000.0
-- because
:t (*) -- (*) :: Num a => a -> a -> a

The Floating class includes Float and Double

sin, cos, and sqrt require instances of Floating


The Integral class includes Int and Integer

Integral numbers are whole numbers

fromIntegral converts Integrals into Floatings

(read "1" :: Int) + 2.5 -- 3.5

2018-08-21

Pattern matching is great, definitely cuts down on if/else branching

one :: Int -> String
one 1 = "Yay!"
one _ = "Oh no"

one 1 -- Yay!
one 0 -- Oh no

Patterns are matched in order, so a catchall should always come last

If there is no catchall, function calls can error, so probably just include a catchall


Tuple pattern matching works like destructuring so

wrap :: (String, String) -> String -> String
wrap (x, z) y = x ++ y ++ z
wrap ("(", ")") "hey" -- "(hey)"

This works on various sizes of tuples


The (x:_) or (x:xs) syntax is very helpful for recursing with a list and also simple things like head

head' (x:_) = x
head' "YO" -- Y

head2 (x:y:_) = [x,y]
head2 [1,2,3] -- [1,2]

As-patterns are cool -- you get your destructured elements as well as a reference to the original argument

firstLetter :: String -> String
firstLetter [email protected](x:_) = "The first letter of " ++ xs ++ " is " ++ [x]
firstLetter "trebuchet" -- The first letter of trebuchet is t

Guards are neat -- they're like a compromise between pattern matching and if/else

one :: Int -> String
one x
  | x == 1    = "Yay!"
  | otherwise = "Oh no"

They work on boolean expressions, can handle multiple arguments


where is used to store values so they aren't re-calculated unnecessarily

sumIs :: [Int] -> String
sumIs xs
  | s < 0          = "Less than zero!"
  | s `mod` 2 == 0 = "Even!"
  | otherwise      = "Odd!"
  where s = sum xs

sumIs [1,2] -- Odd!
sumIs [1,2,3] -- Even!
sumIs [-10] -- Less than zero!

Multiple where values can be provided in the same block

2018-08-28

let/in expressions are similar to where but don't span across guards

quadruple :: Int -> Int
quadruple x = let double = x * 2 in double * 2

They're also usable in more places, like pretty much anywhere

squaresOver50 :: [Int] -> [Int]
squaresOver50 xs = [ sq | x <- xs, let sq = x * x, sq > 50 ]

squaresOver50 [1..10] -- [64,81,100]

2018-09-06

Case expressions are fun

one :: Int -> String
one x = case x of 1 -> "Yay!"
                  _ -> "Oh no"

one 1 -- Yay!
one 0 -- Oh no

repeat' x = x:repeat' x
take 10 (repeat' 'A') -- AAAAAAAAAA

Recursion works how you would expect in a lazy environment