I recently decided to learn Clojure, a Lisp that runs on the JVM. Since it does, it can take advantage of all of Java’s libraries. I’ve programmed in both Common Lisp and Scheme before, and so it wasn’t very difficult to pick up, but it may be harder for someone who hasn’t used a Lisp before.
The best way to learn a language is to use it, so I decided to create it to generate the booklist that is now on my sidebar. This was a very simple task, so it only took me about an hour or so while learning Clojure. The code and my explanations of it and some issues I ran into follow: If you know any ways to improve it, please let me know.
(def book-list {:categories [ {:name "Nonfiction - Technical" :books [ {:title "Hacker's Delight" :asin "0201914654"} {:title "The Craft of Text Editing" :asin "0387976167" :blog "http://nflath.com/2009/04/the-craft-of-text-editing"} {:title "On Lisp" :asin "0130305529"} {:title "Practical Common Lisp" :asin "1590592395"} {:title "Java Puzzlers" :asin "032133678X"} {:title "The Little Schemer" :asin "0262560992"} {:title "The Reasoned Schemer" :asin "0262562146"} {:title "Beautiful Security" :asin "0596527489"} {:title "Beautiful Architecture" :asin "059651798X"} {:title "A Little Java, A Few Patterns" :asin "0262561158"} {:title "The Algorithm Design Manual" :asin "1848000693"} {:title "Programming Language Concepts and Paradigms" :asin "0137288662"} ]} {:name "Nonfiction" :books nil } {:name "Fiction" :books [ {:title "Sensei" :asin "0451411323"} {:title "Angels and Demons" :asin "1416580824"} {:title "Belgarath the Sorcerer" :asin "0345403959"} ]} ]})
This data structure represents the booklist. It is a nested set that contains all the information about the organizational structure of the books. Clojure (or any lisp) allows you so record your data in ways that are both human-readable and trivially computer-readable. All operations to generate the HTML for the booklist page operate on this data structure.
(defmacro str-concat [& body] "Concatenates a list of sequences as a string" `(apply str (concat ~@body)))
Unfortunately, the concat method will not work very well on strings. Since concat works on sequences, one of which are strings, if you just concat a list of strings the result will be a sequence of characters, so you have to wrap it in an (apply str …) to get the value as an actual string. I created this as a macro instead of a function because the laziness of Clojure sequences was giving me problems when printing the final result out.
(def intro-string "These are a few of the books I've read - It is incomplete, but I'll try to keep it up to date and blog about any additional technical books I finish reading.<br>") (def header-link-format-string "<a href="#%s">%s</a><br>n") (def header-format-string "<h2><a name="%s">%s</a></h2>" ) (def book-link-format-string "<a href="http://www.amazon.com/gp/product/%s?ie=UTF8&tag=randmusiofaso-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=%s">%s</a><img src="http://www.assoc-amazon.com/e/ir?t=randmusiofaso-20&l=as2&o=1&a=%s" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />") (def blog-link-format-string "<t><t><a href="%s">Blog</a><br>n")
These are just a few configuration variables that determine how the list is laid out. They ensure that links are to the correct anchor and that books are linked to Amazon and any blog posts properly, without me having to manually add the link in for each book.
(defn generate-header [book-list] "Generates the header of the book-list" (str-concat (for [category (book-list :categories)] (format header-link-format-string (category :name) (category :name)))))
This just loops over the categories in the booklist and creates a HTML link to what will be the main entry. The for macro returns a list of strings, so we have to str-concat them to get our final string. Unfortunately, format can’t refer to one argument multiple times(not that I saw, anyway - if anyone knows how to do this please tell me!), so I have to pass in the category name twice.
(defn generate-body [book-list] "Generates the body of the book-list" (str-concat (for [category (book-list :categories)] (str-concat (format header-format-string (category :name) (category :name)) "<table>" (str-concat (for [book (category :books)] (str-concat "<tr><td>" (format book-link-format-string (book :asin) (book :asin) (book :title) (book :asin)) "</td><td>" (if (book :blog) (format blog-link-format-string (book :blog)) "<br>") "</td></tr>n" ))) "</table>n" ))))
This is similar to the previous function, but more complicated. It generates the headings for each section, then loops through each book in each category and outputs the formatting required to link them to amazon and any blog posts I’ve made. The lazy sequences were very annoying here, forcing me to make four nested str-concats in order to get the output I needed when printed instead of just referring to lazy sequences.
(defn print-html [] "Prints the HTML for the book-list page" (println intro-string) (println (str-concat (generate-header book-list) (generate-body book-list))))
This function essentially just prints the introduction, the header, and the body of the list. Nothing complicated is happening here.
(print-html)
This just calls the previously-defined function, giving me the HTML I need to paste into WordPress to generate a new page.
I’m working on another Clojure projects, so I’ll post more of my thoughts about the language once I finish it.
You should use the AWS API to load in your wishlists. That way you don’t need to manually populate the booklist, and it’ll automatically update. Perhaps you could store an offline copy refreshed by the online one every once in a while?
You’ve written CL and Scheme before and you still place your closing brackets on their own lines? =)
Phil: Yeah, I mostly put them on a seperate line when it’s something I have to change fairly frequently - such as the booklist, which i’m going to be adding new items too and don’t want to navigate to the correct inner bracket, or an function with variable arguments that I play around with (like the concat in generate-body).
Format can refer to the same argument twice eg.;
user=> (format “He%1$s%1$s%2$s W%2$sr%1$sd” \l \o)
“Hello World”
For details on the syntax of format see http://java.sun.com/javase/6/docs/api/java/util/Formatter.html
Have fun with clojure!