The final of the seven languages is Haskell and so far it feels nice. Gone are Clojure's brackets and back to front syntax. Haskell is much more Erlang-esque, which may just be the module declarations and the list comprehensions. It even feels a little perlish with ::s and $s here and there. Tate compares Haskell to Spock from Star Trek and tells us that it makes hard things easy and easy things hard.
Day one starts slowly enough, by now we’re cracking through conceptual territory at a fair whack. A lot of what we cover is the syntactic sugar of concepts we’ve already investigated so the faster pace works well enough for me. The homework today is, to say the least, challenging. Certainly compared with the first day homeworks of the other languages in the book. Well, I’ve got two bottles of San Miguel, a laptop and a decent internet connection! So here we go.
all_evens.hs
How many different ways can you write allEven?
I spent a fair while on this one. Particularly helpful was chapter 5 of Learn you a Haskell for Great Good — great name, great book. I am fairly certain that there are ones of which I didn’t think, but I had fun diving into the Haskell syntax and even skipped ahead of the book.module Main where
-- The original one from the book
allEven :: [Integer] -> [Integer]
allEven [] = []
allEven (h:t) = if even h then h:allEven t else allEven t
-- Using a list comprehension and modulo 2
allEvenListComp0 :: [Integer] -> [Integer]
allEvenListComp0 list = [x | x <- list, x `mod` 2 == 0]
-- Using a list comprehension and the even function
allEvenListComp1 :: [Integer] -> [Integer]
allEvenListComp1 list = [x | x <- list, even x]
-- Using a filter and the even function
allEvenFilter :: [Integer] -> [Integer]
allEvenFilter list = filter even list
-- Using the guard syntax
allEvenGuard :: [Integer] -> [Integer]
allEvenGuard [] = []
allEvenGuard (h:t)
| even h = h:allEvenGuard t
| otherwise = allEvenGuard t
-- Using a right fold with an anonymous function
allEvenFoldr :: [Integer] -> [Integer]
allEvenFoldr [] = []
allEvenFoldr list = foldr (\x acc -> if even x then x:acc else acc) [] list
-- Using a left fold of another left fold with an anonymous function
allEvenFoldl :: [Integer] -> [Integer]
allEvenFoldl [] = []
allEvenFoldl list = foldl(\acc x -> x : acc) [] (foldl (\acc x -> if even x then x:acc else acc) [] list)
All of these methods produce the same result, we’ll have a look at a sample run in a second. But I just wanted to mention the last function which is the most complex. I have to use two left folds because left folding returns our list in reverse order, reverse of reverse yields the correct order. In real life I very much doubt I’d ever use this approach. Here is a sample run. Prelude> :load all_even.hs
[1 of 1] Compiling Main ( all_even.hs, interpreted )
Ok, modules loaded: Main.
*Main> allEven [1,2,3,4,5,6]
[2,4,6]
*Main> allEvenListComp0 [1,2,3,4,5,6]
[2,4,6]
*Main> allEvenListComp1 [1,2,3,4,5,6]
[2,4,6]
*Main> allEvenFilter [1,2,3,4,5,6]
[2,4,6]
*Main> allEvenGuard [1,2,3,4,5,6]
[2,4,6]
*Main> allEvenFoldr [1,2,3,4,5,6]
[2,4,6]
*Main> allEvenFoldl [1,2,3,4,5,6]
[2,4,6]
*Main>
reverse_list.hs
Write a function that takes a list and returns the same list in reverse.
Well, there is a library function called reverse, but rather than cheat and use that I came up with this recursive function. I was a bit troubled about having to use square brackets round h in the last line. But it doesn't work otherwise.module Main where
rev :: [a] -> [a]
rev [] = []
rev (h:t) =
rev t ++ [h]
The function takes an list of arbitrary type (that is the [a]). If the list is empty it returns the empty list. Otherwise it takes the head and tail of the list and appends the head to the tail. Let’s see if it works.Prelude> :load reverse_list.hs
[1 of 1] Compiling Main ( reverse_list.hs, interpreted )
Ok, modules loaded: Main.
*Main> rev [1,2,3,4]
[4,3,2,1]
*Main> rev ["hat","tree","marmelade","plinth"]
["plinth","marmelade","tree","hat"]
*Main>
two_tuples.hs
Write a function that builds two-tuples with all possible combinations of two of the colours black, white, blue, yellow, and red. Note that you should include only one of (black,blue) and (blue,black).
It seemed pretty clear to me that this needed a list comprehension. My function takes a list of Strings and returns a list of tuples of strings. The list comprehension takes as and bs off the list and pairs them up when a is less than b, that is it sorts before it.module Main where
twotuples :: [String] -> [(String,String)]
twotuples list = [ (a, b) | a <- list, b <- list, a < b]
We can have a quick look to make sure all is as we expect.Prelude> :load two_tuples.hs
[1 of 1] Compiling Main ( two_tuples.hs, interpreted )
Ok, modules loaded: Main.
*Main> twotuples ["black","white","blue","yellow","red"]
[("black","white"),("black","blue"),("black","yellow"),("black","red"),("white","yellow"),("blue","white"),("blue","yellow"),("blue","red"),("red","white"),("red","yellow")]
*Main>
times_table.hs
Write a list comprehension to build a childhood multiplication table. The table would be a list of three-tuples where the first two are integers from 1–12 and the third is the product of the first two.
This is almost the same problem as the last one, but without having to support arbitrary input. I take a and bs from the list of integers, and build my tuple with a,b and a*b.module Main where
ints = [1..12]
timesTable = [(a,b,a*b) | a <- ints, b <- ints ]
Prelude> :load times_table.hs
[1 of 1] Compiling Main ( times_table.hs, interpreted )
Ok, modules loaded: Main.
*Main> timesTable
[(1,1,1),(1,2,2),(1,3,3),(1,4,4),(1,5,5),(1,6,6),(1,7,7),(1,8,8),(1,9,9),(1,10,10),(1,11,11),(1,12,12),(2,1,2),(2,2,4),(2,3,6),(2,4,8),(2,5,10),(2,6,12),(2,7,14),(2,8,16),(2,9,18),(2,10,20),(2,11,22),(2,12,24),(3,1,3),(3,2,6),(3,3,9),(3,4,12),(3,5,15),(3,6,18),(3,7,21),(3,8,24),(3,9,27),(3,10,30),(3,11,33),(3,12,36),(4,1,4),(4,2,8),(4,3,12),(4,4,16),(4,5,20),(4,6,24),(4,7,28),(4,8,32),(4,9,36),(4,10,40),(4,11,44),(4,12,48),(5,1,5),(5,2,10),(5,3,15),(5,4,20),(5,5,25),(5,6,30),(5,7,35),(5,8,40),(5,9,45),(5,10,50),(5,11,55),(5,12,60),(6,1,6),(6,2,12),(6,3,18),(6,4,24),(6,5,30),(6,6,36),(6,7,42),(6,8,48),(6,9,54),(6,10,60),(6,11,66),(6,12,72),(7,1,7),(7,2,14),(7,3,21),(7,4,28),(7,5,35),(7,6,42),(7,7,49),(7,8,56),(7,9,63),(7,10,70),(7,11,77),(7,12,84),(8,1,8),(8,2,16),(8,3,24),(8,4,32),(8,5,40),(8,6,48),(8,7,56),(8,8,64),(8,9,72),(8,10,80),(8,11,88),(8,12,96),(9,1,9),(9,2,18),(9,3,27),(9,4,36),(9,5,45),(9,6,54),(9,7,63),(9,8,72),(9,9,81),(9,10,90),(9,11,99),(9,12,108),(10,1,10),(10,2,20),(10,3,30),(10,4,40),(10,5,50),(10,6,60),(10,7,70),(10,8,80),(10,9,90),(10,10,100),(10,11,110),(10,12,120),(11,1,11),(11,2,22),(11,3,33),(11,4,44),(11,5,55),(11,6,66),(11,7,77),(11,8,88),(11,9,99),(11,10,110),(11,11,121),(11,12,132),(12,1,12),(12,2,24),(12,3,36),(12,4,48),(12,5,60),(12,6,72),(12,7,84),(12,8,96),(12,9,108),(12,10,120),(12,11,132),(12,12,144)]
map_col.hs
Solve the map-coloring[sic] problem using Haskell.
Oh man. This was really hard. If I’d have done it in perl it might have taken a while. But this took a few hours and several versions. I’m still not entirely sure that this is the best approach, indeed Alberto Peña Abril’s solution is far more elegant than mine. My version incidentally, is pretty similar to this one by Boy Plankton.
We start with lists of the states and the colours. Then I have to create a function to look up the bordering states of the state in which I am interested. WHich is frankly a pain. I could have done with an hash/map/dictionary/associative array at this point. Then I build a function to look up the colours of the bordering states which have colours, using the accumulator defined later. After that I need an available_colours function to filter the available colours, basically all the colours which are not the colours of a bordering state. Finally the colour_map states function plugs everything together using this funky where syntax to define a sub function that loops through the states and colours accumulating state/colour pair lists to return into an accumulator. I learned a lot of syntax that we hadn’t yet covered doing this excercise and I almost gave up on it a few times.module Main where
states = ["Tennessee", "Mississippi", "Alabama", "Georgia", "Florida"]
colours = ["red", "green", "blue"]
-- I really wanted a hash for this lookup. sigh.
bordering_states :: String -> [String]
bordering_states my_state
| my_state == "Tennessee" = ["Mississippi", "Alabama", "Georgia"]
| my_state == "Mississippi" = ["Tennessee", "Alabama"]
| my_state == "Alabama" = ["Tennessee", "Mississippi", "Georgia", "Florida"]
| my_state == "Georgia" = ["Tennessee", "Alabama", "Florida"]
| my_state == "Florida" = ["Georgia", "Alabama"]
| otherwise = error "Some crazy state with which I can't deal"
-- pass it your accumulator and it tells you which bordering states have
bordering_state_colours :: ([[String]],String) -> [String]
bordering_state_colours (states_with_colours, my_state) =
[last x | x <- states_with_colours, y <- (bordering_states my_state), head x==y ]
available_colours :: [String] -> [String]
available_colours [] = colours
available_colours (h:t) = [poss | poss <- available_colours t, h /= poss ]
-- acc accumulates coloured in states and their colours its a nested list type of structure
-- colour in is a recursive function, I use pattern matching to
colour_map states = colour_in [] states
where colour_in acc (h:t) = colour_in ([h, head ( available_colours ( bordering_state_colours (acc, h) ))] : acc ) t
colour_in acc _ = acc
I almost danced for joy when this worked!Prelude> :load map_col2.hs
[1 of 1] Compiling Main ( map_col2.hs, interpreted )
Ok, modules loaded: Main.
*Main> colour_map states
[["Florida","red"],["Georgia","green"],["Alabama","blue"],["Mississippi","green"],["Tennessee","red"]]
*Main>
Outro
The first day was definitely not for the faint of heart, but being stretched this much has made me feel like I know a lot more of the language than if I had just been able to use the book to look up answers. I am starting to get on rather well with Haskell’s clean and minimal syntax and despite a fair amount of swearing during the map colouring excercise.