Seven Languages In Seven Weeks: Clojure - Day 1
After a few weeks off from my Seven Languages in Seven Weeks book by Bruce Tate, I just started the sixth language: Clojure. Clojure is a version of LISP that runs on the JVM (Java Virtual Machine). LISP comes from the phrase, "List Processing;" the reason for this will become evident the moment you look at the code which consists of list of lists of lists. Back in college, my Artificial Intelligence (AI) teacher joking defined LISP as "Lots of Irritating Superfluous Parenthesis."
To be completely honest, LISP was not something that I could wrap my head around in college. In my AI class, we needed to write a LISP program that moved a robot through a maze (digitally); I was unable to complete the assignment. I brought much shame upon my family.
This time, it's personal!
HW1: Implement a function called (big st n) that returns true if a string st is longer than n characters.
Before we get into the code, I should probably mention that, unlike any of the other languages that we've looked at so far, Lisp uses Prefix Notation. This is a type of structuring in which the first element in a list is the function and the the rest of the elements in the list are all parameters to that function. So, in our assignment, "big" is the function and "st" and "n" are the two parameters. This style holds true for all operators as well, including "=" and ">", which you'll see in the following code.
;-- Implement a function called (big st n) that returns true if a
;-- string is longer than n characters.
;-- Define our function.
(defn
big
[input targetLength]
(> (count input) targetLength)
)
;-- Now, let's test our function.
(println (big "" 3))
(println (big "h" 3))
(println (big "he" 3))
(println (big "hell" 3))
(println (big "hello" 3))
Here, we are using the function, defn, in order to define the "big" function. This function (defn) takes three parameters: the name of the function we are defining, the vector of arguments, and the body to execute. As you can see (or try to see), our function returns whether or not the length of the incoming string is bigger than the target length.
We are then testing the big function over a set of increasingly long string values. When we run the above code, we get the following console output:
false
false
false
true
true
As you can see, once we reached a string length of 4 - "hell" - our big function started returning true.
HW2: Write a function called (collection-type col) that returns :list, :map, or :vector based on the type of collection col.
Clojure has a bunch of core functions for testing data types. As such, for this solution, I'm just throwing those in a (cond) statement - Lisp's version of a Switch/Case statement - and returning the appropriate symbols:
;-- Write a function called (collection-type col) that returns
;-- :list, :map, or :vector based on the type of collection.
;-- Define our function.
(defn
collection-type
[input]
(cond
(list? input) :list
(map? input) :map
(vector? input) :vector
)
)
;-- Define our various data types to test again.
(def myList '(1 2 3))
(def myMap {:a 1, :b 2, :c 3})
(def myVector [1 2 3])
;-- Output the class types for the three test values in order to
;-- make sure that we know we are dealing with the correct test
;-- data types.
(println "myList:" (class myList))
(println "myMap:" (class myMap))
(println "myVector:" (class myVector))
(println "")
(println "myList:" (collection-type myList))
(println "myMap:" (collection-type myMap))
(println "myVector:" (collection-type myVector))
;; And one test for nil on an unexpected collection type.
(println "Nil Test:" (collection-type #{}))
Once I have my (collection-type) function defined, I then create a number of data structs against which to test the function. When we run the above code, we get the following console output:
myList: clojure.lang.PersistentList
myMap: clojure.lang.PersistentArrayMap
myVector: clojure.lang.PersistentVectormyList: :list
myMap: :map
myVector: :vector
Nil Test: nil
As you can see, all my tests return the appropriate value except for the Set - my Nil test. Because my (cond) statement does not have a condition for Set values, it returns nil which becomes the result of the (collection-type) function call.
When I first approached this problem, I tried to come up with my own way to test for type. Before I found the (list?) and (map?) style functions, I found the (instance?) function. As such, I thought I could check to see if the incoming collection was an instance of the various types of data I was looking for.
This approach proved to be immediately problematic as the same type of data appears to be modeled by difference classes. To see what I mean, take a look at this code:
user=> (class '(1 2 3))
clojure.lang.PersistentList
user=> (class '())
clojure.lang.PersistentList$EmptyList
Both of these lines check the class for the given list. However, notice that the empty list is actually modeled by a different class (what appears to be an inner class). Furthermore, these classes are not equal:
user=> (= (class '()) (class '(1 2 3)))
false
... and, the empty list is not an instance of the populated list:
user=> (instance? (class '(1 2 3)) '())
false
As you can see, there doesn't appear to be any form of implicit up-casting of data types when using the (instance?) function. In the end, however, it didn't matter because the (cond) approach is definitely way cleaner and more concise than my first idea.
As someone who is a stickler for code formatting and readability, I suspect that finding the right mix of line breaks in Lisp will be challenging. However, after this homework, I am remaining hopeful that this time around, I'll be able to figure out what this Lisp thing is all about.
Want to use code from this post? Check out the license.
Reader Comments
Clojure is mylanguage for this year. I attended the 2010 ClojureConj here in RTP and it was great. There will probably be another one in October 2011 so come on down to North Carolina.
I went to hear Stu Halloway last night at the Ruby meeting in RTP. He did a talk "Clojure for Rudy Devs" that was good in content and due to the fact that he is a great speaker.
I also have been trying to learn Clojure on my own so it has been a bit of a struggle. I look forward to more of your posts.
@Roger,
Tate actually mentions Halloway in the book as the author or Programming Clojure [Hal09]... I think; I am can't find the reference at the moment. It would be cool to hear him talk.
How did you get started with Clojure anyway? Having found Lisp so challenging in college, I am sure I would have never gone down this road if the book didn't require it; although, the door swings both ways - now that I am trying it, I am feeling really good about myself as I make progress. Like I said in the post, this time it's personal ;)
I actually just posted Day 2 if you are interested:
www.bennadel.com/blog/2100-Seven-Languages-In-Seven-Weeks-Clojure-Day-2.htm
It took me a frustrating 4 hours, but it's starting to make more sense.
Halloway is CEO of Relevance over in Durham so he is local to me. You can see a number of his talks on the InfoQ site <http://www.infoq.com/Clojure> Stu and Rich Hickey videos are pretty good.
I had heard Sean Corfield talk some about Clojure on-line and then I got wind of the first ClojureConj conference that was to be held near where I work in RTP. I contacted Sean for some guidance on whether it would be a good event for me. He was very supportive and wished that he did not have a conflict with it. It was inexpensive and I didn't have to travel so I went and was glad I did. It was a very supportive group of people similar to the CF crowd.
I had tried to get started with just the book, but the conference really did help. The on-line videos helped a lot too just to get familiar with the concepts. I wish they would do a noob class along with the next conference. It will probably come back here again sometime in October 2011.
I'm still learning and definitely at the noob stage. I don't have a lot of time to learn things and I spent a good bit of time learning .NET stuff last fall so Clojure fell to the side until just the last few weeks. Good luck with it, Roger (hope you make it to NCDevCon 2011)
Great to see you being more successful with Clojure than you were with Lisp all those years ago! Despite the four hours for each day of exercises, I hope you ultimately enjoyed the experience and learned a lot - things you can apply to solving problems in other languages?
I'm going to make one idiomatic observation and that's on parentheses placement: the standard idiomatic Lisp layout for parentheses is to close them all on one line rather than on separate lines indented as you have them.
See http://blog.fogus.me/2010/08/30/community-standards/ for an amusing commentary on the discussion of Clojure / Lisp parentheses placement and the related Clojure Golf post for more traditional code layout.
Roger, I'll see you at Clojure Conj 2011 I hope. Ben, maybe see you there too? :)
@BenNadel, you need better spam protection on your blog dude!