Today’s excercises seemed a little more straightforward than in previous languages. I may be simply getting brainier, of course. I doubt that.
It is something of a lightning tour that we take through Clojure; a lot of stuff gets covered in a little time. The chapter covers recursion, including how to do tail recursion; sequences; lazy evaluation; the defrecord and protocol approach to implementing types for the JVM and macros. Enough to keep me scratching my head for a while. Though it was mostly due to wrongly parentheses, the headscratching. Let’s have a look at the homework anyhow.
Unless.clj
Implement an unless with an else condition using macros.
This seemed straightforward and took all of five minutes. After typing mine up I had a browse round some bloggers who had had a crack and found a much nicer solution by yannick. Yannick’s version makes the otherwise bit optional. For mine the else is compulsory. Not as cool.
(defmacro unless [test body otherwise]
(list 'if (list 'not test) body otherwise))
We'd best check that everything works as expected.
user=> (unless false (println "true") (println ("false")))
true
nil
user=> (unless true (println "false") (println "true") )
true
nil
user=> (unless true (println "true") (println "false"))
false
nil
user=> (macroexpand '(unless condition body alt-body))
(if (not condition) body alt-body)
user=>
Super. Excercise one done.
Protocol_record.clj
You can implement something like a Java class — a type in Clojure parlence — using the defrecord function. This is complemented by an equivalent of an interface, known as a protocol which your types may implement. I found this part of Clojure a bit fiddly, most likely as I’m just getting my head around the Clojure way. I can see a few ways where I may have avoided reusing code in my answer if only I knew the Clojure lingo.
Write a type using a defrecord that implements a protocol
I struggled with parentheses a little when working this out. It isn’t the most original class to choose I’m afraid. But it serves my purpose adequately.
(defprotocol Animal
(legs [num_legs])
(noise [sound])
(food [eats])
(eat [food])
; idiomatic way to name variabls?
)
(defn make_noise [noisemaker noise]
(str noisemaker " says " noise)
)
(defn munch [diner food]
(str diner " munches on " food)
)
(defrecord Kitten [name] Animal
(legs [_] 4 )
(noise [this] (make_noise (:name this) "meee-owww") )
(food [_] "mouse")
(eat [this] (munch (:name this) (food this) ))
)
(defrecord Chicken [name] Animal
(legs [_] 2 )
(noise [this] (make_noise (:name this) "cluck") )
(food [_] "corn")
(eat [this] (munch (:name this) (food this) ))
)
(def kitteh (Kitten. "Kitteh"))
(def camilla (Chicken. "Camilla"))
(println (noise kitteh) )
(println (legs kitteh) )
(println (food kitteh))
(println (eat kitteh))
(println (noise camilla) )
(println (legs camilla) )
(println (food camilla) )
(println (eat camilla))
OK cool, let’s just see what that does then. $ java -cp /usr/share/java/clojure.jar clojure.main protocol_record.clj
Kitteh says meee-owww
4
mouse
Kitteh munches on mouse
Camilla says cluck
2
corn
Camilla munches on corn
End of day two
And with that day two is over. I rather enjoyed Clojure’ 's simplicity. I found types a little fiddly, and I’ve ben havingfun with my VimClojure environment. The main VimClojure irritation is its smart parenthesizing. I really must consult the manual and find out how to delete unwanted parentheses. In fact that’d be one of my annoyances with Clojure. Of course the Lisp mystics will claim that you only see the parens when they aren’t right. But it’ll take some time for me to get to that stage. A whole heap of time.