Caffeinated Simpleton

First Stab at Learning Clojure

I’ve decided I want to learn clojure. Clojure is a lisp dialect that runs on the Java Virtual Machine. It’s all the rage these days, and I want to be cool. I’m going to post my trials and tribulations here for others to read and hopefully learn from. If you are a better hacker than I and see problems with what I’ve posted, please comment! This is going to be a multi-day process, and a slow one as I don’t have much time, but I hope to finish a project within the next six months.

There are a couple problems with Clojure and I. First of all, I’ve never programmed in any lisp dialect. Second of all, I’ve never programmed in Java. Clearly I do not have the tools to get into this, but I’m going to go for it anyway.

The Project

I’ve been thinking a lot about Twitter recently, and there are some really cool applications of Twitter that I find kind of exciting. However, Twitter’s signal to noise ratio can quickly become unbearable. So, I thought I’d come up with a Twitter feed app that will organize people you care about, conversations you want to monitor, tweets around where you are, and the regular twitter feed into a sort of Twitter portal. It should be fun and easy, and it’s not the project that’s important. It’s learning Clojure. I’m going to call it Flockr for maximum Web 2.0 street cred. You can find the source on github.

Getting Started

I’m using Compojure as my web framework. I like web frameworks, especially for little projects like this that don’t need any specialized plumbing. Compojure isn’t terribly mature, but it should be good enough to get me off the ground.

Installing compojure is easy. Just download, compile with ant, and put the resulting jar file in your classpath. Being a vim guy, I wanted clojure syntax highlighting and auto-indenting. There’s a vim package available to do that stuff, and I recommend it.

The First Page

This is basically copied straight off the compojure “Getting Started” page. However, this is the first time I’ve really looked at lisp, so let’s see what this does.

Clojure Basics (or at least, what I’ve inferred about Clojure thus far)

Parentheses indicate a list. I’m a big C/Python/JavaScript guy, so this a bit strange, but I can deal. Evaluation is simply evaluating every list that is passed into the REPL. This first program has three things that are evaluated. The first is the namespace. The second is the servlet, and the third is the server. Let’s that a closer look at the first line.

Returns the result of the “ns” function, which creates a new namespace called “index” (in this case). Within this namespace, we are dependent on the “compojure” namespace. We pass the “:use” key to indicate this.

The colon syntax is something I’m still getting used to. It means a key, which makes sense in a map, but it would appear that you can also put keys in other data structures.

The only other things you need to know to understand the rest of the code is “[]” and “{}”. “[]” is a vector. I’m not certain I understand when to use vectors versus lists except for performance. “{}” is a map. A colon indicates a key (IE :port), the thing following it is the value (IE 8080). Commas are optional, and don’t seem to be used in examples at all.

Doing something real

Ok, so we’ve got something up and running. That’s good. Now let’s grab some twitter statuses. Unfortuneatly, it’s past 4 in the morning. This will have to wait for another day.

Tags: , ,

This entry was posted on Sunday, March 8th, 2009 at 7:28 pm and is filed under Development. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

  • weavejester
    You don't need to write html twice: the html function handles nested vectors easily enough. I'd also tend to separate out the html generation into its own function and keep the servlet definition lightweight. It doesn't matter so much when you only have two lines of HTML, but keeping your views separate from what is essentially your routing table makes a lot of sense when things start to get more complex.

    Here's my fork of your gist: http://gist.github.com/76507
  • Awesome, thanks! I wonder if there's a way for me to pull your changes...

    I totally agree with the bit about abstracting out html. I'm planning on re-factoring frequently as I go along, to kind of show an evolution from the simplest case to a well designed piece of software.

    It's a bit easier to enforce this in other web frameworks that include the plumbing for these kind of abstractions in the first place, but I think I prefer this for learning the language. It abstracts away the details of dealing with HTTP requests and routing without getting in the way of the fun pieces. We'll see what comes out down the line as a result.
  • Matt Curtis
    Lisp formatting usually gathers up all the closing parens on the last line of the form. For ex:

    (defservlet home
    (GET "/"
    (html [:h1 "Flockr"]
    (html [:h2 "Twitter Portal"]))))

    Doesn't take as long to get used to this style as it first appears, and it saves a heap of vertical space (like Python).
  • Matt Curtis
    er, but obviously indented... comment didn't indent :)
  • Interesting...I didn't even notice that. While experienced lispers may not like this, might this make it easier for people who are used to Ruby or Python to read lisp?
  • Jules
    Remember that parens are used for function calls. The way you wrote that looks like this to a Lisper:

    calculate(5
    )

    You wouldn't write that in other languages either.
  • I would if there was a lot of stuff in the function call, which is that case with Lisp.

    However, I can see that it's not a popular decision. I hate relying on text editor features to write correct code, but if it's the way things are done...
  • Keeping your closing parens on their own line instead of putting them all on one line creates a lot of unnecessary line noise.

    As far as readability, indentation should guide readers, not parens. If you try to tell what's going on by matching each paren one-by-one, you'll go crazy. If the code is properly indented, you won't have to; you let the indentation be your guide.
  • For me, it wasn't about reading the code, it was about writing it. When I can line up the parentheses, it's clear that I've closed them all. I've changed it now to keep you fine folks happy, but if it ends up being inconvenient, I'll go right back to every parenthesis being on its own line.
  • Commas are whitespace in clojure. Use 'em if it makes you feel good, otherwise don't.
  • Marc
    You might want to change the font size of your code so it's a little bigger.
  • It's a bit better now, but I think I need to redo the site and make the fonts bigger.
  • "Vectors are also ordered collections of items. Their implementation is similar to arrays. They are ideal when items need to be retrieved by index, but not efficient when new items need to be added or removed quickly"
    ;-)
    http://ociweb.com/jnb/jnbMar20...
  • I understand the performance implications, but do they have differences beyond these? Do they behave differently within Clojure?
  • weavejester
    There's more functions that work with vectors, such as conj and assoc. Vectors are also more concise, e.g. [1 [[2] 3]] as opposed to (list 1 (list (list 2)) 3)).
  • Ah, thanks.

    I was under the impression that () was also a list. So you could do (1 ((2) 3)).

    Also, it looks like "conj" can be applied to any sequence, but you're right about "assoc". There are others too that only work with vectors according to http://clojure.org/data_struct....
  • weavejester
    Clojure, like all lisps, treats the first item in a list as the thing to call, and the rest of the items in the list as its arguments. So (f x y) is equivalent to f(x, y) in Python. If you want to create a list, you have to write (list f x y), which is equivalent to list(f, x, y) in Python.

    Vectors, on the other hand, don't treat the first item any differently. So [f x y] in Clojure is the same as [f, x, y] in Python.

    Lisps like Clojure also have the notion of quoting, which languages like Python have no direct analogy to. By quoting an expression Clojure, we tell the compiler not to evaluate its contents. So (quote f x y) or '(f x y) will return a list of unevaluated symbols. We can evaluate them later on with eval, if we so desire, so (eval (quote f x y)) is equivalent to (f x y).

    Oh, and you're right about conj - though in vectors conj appends to the end, whilst in lists it appends to the beginning.
blog comments powered by Disqus