We’re near the end and this week (cough, the last 2 days) I did CLojure. I used Clojure some years before, I even got “Programming Clojure” standing in my shelf, although I with it were “The Joy of Clojure” instead. Nevermind.
Clojure is powerful. Actually, Clojure is immensely powerful and super-concise. My experience is whenever I ask for code review on the Clojure IRC channel or mailing list, they reduce my code to 1/3 of it’s length. Form the book, it was obvious that Bruce Tate liked Clojure as well, unlike e.g. Scala, so in the Clojure chapter he also described what was not covered in depth.
Personally, I could have gone without defrecord
and defprotocol
, as the
introduction was kinda weak and did not make much sense. The usage of protocols
felt incredibly forced and the homework where one should design and implement a
protocol was difficult, because I had no idea what to implement at all. So I
made a protocol for ranting.
But this time, the homework was at least not that boring, in general. For
example, one was to implement unless
, so I actually went the extra mile and
made it work just like if
which means that it can take an optional else
clause but does not have to.
1 2 3 |
|
So the first version expands into a recursive macro call with the second
argument set to nil, in a similar way like if
works. Coming from Scheme and
syntax-rules
I think this looks kinda ugly to construct these things with
literal calls to list
and manual quoting. Well, I guess it is simpler and
simplicity is a big proint for Clojure. And furthermore
I can keep my syntax-rules if I like.
Day 3 had an interesting/tricky task, the Sleeping barber problem,
where one had to implement a concurrent solution using the Clojure primitives.
Turned out to be tricky, because I’m not really sold on agents. Implementing
this problem with Actors would have been easy/trivial, just send some messages
around. With agent
, you do it the other way ‘round: instead of sending
messages to functions, you send functions to threadsafe values.
Changing my thinking took quite forever, but the first step to get to a
solution was to define an agent
. The first breakthrough was that barber was
an agent of the number of people he serviced. So you could implement a function
that pauses for 20ms (“working”) and then increments the counter. So, maybe it
would make sense to make the number of free waiting spots an actor too? I did
this and implemented a function to try to add a customer to the waiting queue
(try-occupy
) and turn him down if it is full already. Freeing a seat is easy
(when the barber finishes a customer and gets a new one), just increment the
number of free seats in free-seat
.
Now that you have a function to free-seat
, you need to tell the barber to
free one seat before going off to work on the customer in cut-hair
.
If you run it on this point, nothing happens, because no customers arrive. This
kinda ticked me off because now we’d need to create a thread that tries to
occupy a seat every 10 to 30ms and this did not seem elegant. Yet at the end, I
did not see a better solution so I helped myself to a function that sleeps and
recursively creates new customers, client-arrive
.
When you try starting it at this point, you realize your waiting queue is full
with 3 customers but the barber does nothing. So yet again, we need a function
that moves people from the waiting queue to the barbers chair, move-to-chair
.
Then just start these two threads, wait for 10 seconds and check the value of barber. For me, in the initial run on Clojure 1.4.0 it was around 250 clients serviced which was rather poor, with Clojure 1.2.1 it was three-hundred-something which was better but still poor. I considered running it on a faster computer and it dropped to 122 customers which was really awful.
After some head-scratching I tried putting the move-to-chair
thread to sleep
for 1ms, to prevent it from gobbling up all computation. Lo and behold, 480-490
customers out of a theoretical maximum of 500, I’d say this is pretty decent.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
To my great surprise, I had absolutely no correctness problems at the first try. Maybe it paid off that I was drawing the things on a piece of paper to compose my thoughts?
I was curious how other people solved this and to some point I was actually
quite disappointed by some solutions. Ben’s takes the cake, because he
writes Clojure with Java syntax which makes my eyes bleed (actually, he writes
Io like this too) and his solution is verbose and awkward. This awkward-syntax
thing is also in Bash-Shell’s code (seriously, camelCase in Lisp‽) but
the actual solution behind it it is similar to mine. Devnoo again is
awfully Java-istic. But there is hope, Yannick’s solution seems to me
quite idiomatic and Frédéric’s solution is so immensely similar to
mine, I was actually wondering. Actually, after I wrote mine, I took some
inspiration from him, like the fact that free-sets
should better be an atom
and not an agent
as well as to use future
instead of the (.start (Thread.
function))
clutch I had before.