Charlie Harvey

Haskell — Day One

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.


Comments

  • Be respectful. You may want to read the comment guidelines before posting.
  • You can use Markdown syntax to format your comments. You can only use level 5 and 6 headings.
  • You can add class="your language" to code blocks to help highlight.js highlight them correctly.

Privacy note: This form will forward your IP address, user agent and referrer to the Akismet, StopForumSpam and Botscout spam filtering services. I don’t log these details. Those services will. I do log everything you type into the form. Full privacy statement.