And so, to Haskell!

Wrapping up Clojure

I really enjoyed what i learned about Clojure last week. I notice an interesting thing: if i don’t do the exercises, i feel as if i don’t like the language. But when i actually try them out and solve a few problems for myself, i find i like it.

I like Clojure but i think that’s really because i like Lisp. For me, Clojure just seemed to add a bunch more keywords that i had to remember. Do i really need defprotocol and defrecord, just because they map to Java interfaces and types? I’d rather do without them. And the lack of support for automatic tail-optimised recursion disappointed me. Sure, you can do it explicitly … and that introduces another two Clojure keywords.

I want to get back to the day 3 exercises at some point, but Seven Languages in Seven Weeks is fast-paced and before i know it we’re into the last language!

And now, Haskell

I somehow feel that the whole book has been leading to this. We’ve seen functional elements in most of the languages, but now we get to a language with no excuses, no exceptions. It is uncompromisingly functional. And i love it.

I like strong typing, i like immutability, and i like the power of functions. Haskell is bringing them all together for me, as if it was my perfect dream language, created just for me! Well, i’m only just starting, so we’ll see if this perception continues :)

Just looking at the examples, and trying them out, i get an overall feeling of beauty in simplicity. Haskell is wonderful.

For example

Filtering a list to just the even numbers in the list. The book gives an example using recursion:

allEven :: [Integer] -> [Integer]
allEven [] = []
allEven (h:t) = if even h then h:allEven t else allEven t

The pattern match says that when you get to an empty list you return the empty list.

The tail-optimised recursion checks whether the head of the list is even, and if so includes it in the result, along with the even numbers of the rest of the list. Otherwise, it doesn’t.

We can also solve it with list comprehension. I first tried this:

allEven :: [Integer] -> [Integer]
allEven list = [if even x then x else 0 | x <- list]

I was putting the check into the expression and actually replacing odd numbers with zeros. Not a great solution. Then i remembered we can put a filter on the input side to ensure that only even numbers go in. Suddenly it becomes beautifully simple:

allEven :: [Integer] -> [Integer]
allEven list = [x | x <- list, even x]

Where Clojure felt cluttered with syntax, Haskell feels very minimal and concise.

Reversing a list

Well, this is too easy, so i expect they want me to do something more than just use the built in reverse keyword:

reverseList :: [Integer] -> [Integer]
reverseList list = reverse list

I think recursion is going to be the way to go with this. Split the head and tail and stick them back in the other order. Now then, what’s wrong with this?

reverseList :: [Integer] -> [Integer]
reverseList [] = []
reverseList (h:t) = reverseList(t):h

It gives a compile error:

Couldn't match expected type `Integer' with actual type `[Integer]'
In the return type of a call of `reverseList'
In the first argument of `(:)', namely `reverseList (t)'
In the expression: reverseList (t) : h

It must have something to do with type checking when constructing a list. I wondered whether it needed a few more guards:

reverseList :: [Integer] -> [Integer]
reverseList [] = []
reverseList [x] = [x]
reverseList [x,y] = [y,x]
reverseList (h:t) = reverseList(t):h

No. I think the problem is you can’t use this h:t style list construction with the way i’m imagining you can.

Whilst you can do this:

1:[2,3]

You cannot do this:

[2,3]:1

Basically, the head has to be an element, and the tail has to be a list.

However, we can use addition to join lists:

[2,3] ++ [1]

So the answer is:

reverseList :: [Integer] -> [Integer]
reverseList [] = []
reverseList (h:t) = reverseList(t) ++ [h]

Enough for now

This is taking a long time and it’s probably getting boring for my readers!

I will try to do a bit more on Haskell and publish it to my github: sermoa/7languages7weeks/week7-haskell

Advertisements

Week 6 Day 2 – Clojure macros and protocols

Clojure uses macros to delay execution. We are using this feature to define an unless function.

Here’s an example of an unless that does not work because it is defined with defn:

(defn unless [test body]
  (if (not test) body))

Whatever i give this function, whether the test evaluates to true or false, the body is also evaluated and placed on the stack ready to be returned. This is not what we want, so we use a macro instead.

(defmacro unless [test body]
  (list 'if (list 'not test) body))

We have to use a kind of ugly syntax, explicitly declaring a list and using the apostrophe to delay execution. Clojure uses macroexpand at the right time to evaluate the right bit of code.

We can add an else portion to the function like this:

(defmacro unless [test body other]
  (list 'if (list 'not test) body other))

Protocols

I don’t know enough about Java for this to be deeply meaningful, but i understand the principle of a java interface being something of a contract that a class has to meet.

Clojure, being a JVM language, also has a way of implementing interfaces. It calls them protocols.

I had a really hard time thinking of an example to use for the exercise. It simply said, “Write a type using defrecord that implements a protocol”. Not much to go on. But, looking ahead to day 3, i see we’re going to need bank accounts so i’m going to try that.

I’d like my protocol to look like this:

(defprotocol Account
  (total [c])
  (credit [amount])
  (debit [amount]))

An account has three functions: a total for reporting the amount in the account, and a credit and debit function for putting money in and out.

Now to implement it:

(defrecord BankAccount [opening-balance]
  Account
  (total [c] opening-balance)
  (credit [amount] (BankAccount. (+ opening-balance amount)))
  (debit [amount] (BankAccount. (- opening-balance amount))))

Notice that when i credit or debit a bank account i’m actually getting back a brand new one. Although Clojure doesn’t really enforce immutability, it’s easier to use it than not.

Now let me try to open a bank acccount with £100.

(def account (BankAccount. 100.00))
#'user/account
account
#:user.BankAccount{:opening-balance 100.0}

WOOHOO! That looks better than i expected!

Then i made a proper noob error:

(account total)
java.lang.ClassCastException: user.BankAccount cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

Of course, reverse notation! It’s a list, so i have to call the function first:

(total account)
100.0

Brilliant. Shall we try withdrawing some money?

(debit account 20.0)
java.lang.IllegalArgumentException: No single method: debit of interface: user.Account found for function: debit of protocol: Account (NO_SOURCE_FILE:43)

I’ve got a feeling this is to do with the number of arguments. The first argument to the function is always the instance, the ‘self’. So the protocol needs to understand that:

(defprotocol Account
  (total [instance])
  (credit [instance amount])
  (debit [instance amount]))

The implementation in this case doesn’t care about the instance, so it just uses an underscore:

(defrecord BankAccount [opening-balance]
  Account
  (total [_] opening-balance)
  (credit [_ amount] (BankAccount. (+ opening-balance amount)))
  (debit [_ amount] (BankAccount. (- opening-balance amount))))

Now to try it out:

(debit account 20.0)
#:user.BankAccount{:opening-balance 80.0}

YAAAAYY!! And of course, i can call the total on that new bank account that was returned:

(total (debit account 20.0))
80.0

I can also take that bank account containing £80 and credit it with £60:

(total (credit (debit account 20.0) 60.0))
140.0

Jolly good show.

Week 6 Day 1 – Feeling my way in Clojure

So i am starting to learn a bit of Clojure. It’s not completely alien to me because i’ve done a little bit of SICP with Scheme, another variant of Lisp. The syntax seems quite logical to me, and i like its simplicity.

At the moment we seem to be running everything within the console rather than writing to a file. So i’ll just paste what i’m learning in here.

Lists, Maps, Sets and Vectors

This is quite a lot to take on board. Let me see if i can distill it.

Lists and vectors are ordered. Sets and maps are unordered but can be sorted with the sort and sort-map functions, respectively.

Use lists for code and vectors for data. Vectors are optimized for random access. Maps are key-value pairs. Sets and maps are magically also functions, which to me seems really bizarre but powerful!

Functions

We define data with def and a functions with defn. I didn’t know that you can add an optional string for documentation of a function. That’s nice. Parameters go as a vector, and then it’s the body of the function.

There is a cool trick with destructuring the parameters: if you’re passed more than you actually need, you can pull out just the bits that you want to work with. I see similarities from Scala and Erlang pattern matching.

First exercise

Implement a function called (big st n) that returns true if a string st is longer than n characters.

(defn big [st n] (> (count st) n))

defn defines a function, the name is ‘big’, there are two parameters and we check whether the length of the first is greater than the second.

Second exercise

Write a function called (collection-type col) that returns :list, :map, or :vector based on the type of collection col.

First i tried this just to see what would happen:

(defn collection-type [col] (type col))

Actually something quite interesting happens:

user=> (collection-type '(:r2d2 :c3po))
clojure.lang.PersistentList

user=> (collection-type [:hutt :wookie :ewok])
clojure.lang.PersistentVector

user=> (collection-type {:darth-vader "obi wan", :luke "yoda"})
clojure.lang.PersistentArrayMap

I thought about doing a grep for the last word starting with a capital letter, lowercasing it, prefixing it with a colon … that would have been neat but it would also require clojure-contrib (community created extensions) which i couldn’t find out how to include.

So i’m not going to do that.

Fortunately there are shortcut functions we can use:

(defn collection-type [col] (vector? col))

This will return true if the collection given is a vector. There are similar functions for list? and map? so we can use some kind of test to see what we get.

I wanted some kind of if function that could take multiple options followed by results. The Clojure answer to that is a cond function.

And here it is (updated to close all the parentheses on one line as suggested by Tom):

(defn collection-type [col]
  (cond
    (list? col) :list
    (vector? col) :vector
    (map? col) :map))

I can learn to love parentheses! :)

End of Erlang; start of Clojure

Although i didn’t do much Erlang, Alberto did! :) Looking at this blog post allows me to appreciate Erlang through Alberto’s eyes: Erlang Day Three

Seriously impressive concurrency and fault tolerance. It’s just a simple example, but it shows us something that cannot be killed no matter what you try to do to it. You can easily see how this scales up to huge fault-tolerant telecommunications systems.

Thank you to Alberto for leading the group discussion today, that was great.

Now over to Clojure

And so we start Week 6! Clojure is a version of Lisp that runs on the Java Virtual Machine. So we have lists (obviously!), functions, dynamic typing, immutable state … ahh, well, no. As we now know, for concurrency it is best to write functions without side effects. However, Clojure includes a few tricks to help us out with mutable variables, such as transactional memory and encapsulated access via agents. I’m a little disappointed actually, i was growing rather fond of immutability.

Installing Clojure

I think i probably installed more than was necessary. I’m on Mac OS X with homebrew. I suggest you simply try:

brew install leiningen

Leiningen is a simple build tool that makes it easier to work with Clojure.

Having done that you can start a new project like so:

lein new day1

It sets up an environment for you to use and a few .clj files to get you started (including one to help you write tests!)

Now in the day1 directory you can do:

lein repl

It downloads a couple more files and starts up Java with the Clojure libraries, putting you into a Clojure console.

If that doesn’t work for you, i also did:

brew install clojure
brew install repl

But i think leiningen was all i really needed.