Sunday, September 26, 2010

Lesson 4 - Anonymous functions in Clojure

Ok, now that we have done a simple web page, let us add a table to that page. This table must be created from given rows of data as shown below in the example row.
["Bob" "bob@example.com" "29"]
Thats not all. We need to first convert this row of data into a structure required by the prxml function, before prxml can generate the actual xml.

(defn gen-row [row]
  (reduce
    (fn [arr elem]
      (conj
        arr
        [:td elem]))
    [:tr]
    row))


(println (gen-row ["Bob" "bob@example.com" "29"]))

Run this program and you should get the following line printed to your terminal.
[:tr [:td Bob] [:td bob@example.com] [:td 29]]
Ok. So this is what we wanted. Look at the last line of the code. We first call a function called println, which prints a string to the stdout adding a line feed to the end of the output. To println we pass a call to our own function called gen-row to which we pass our row vector. gen-row takes one vector as its argument and returns another vector structured for the prxml function.

The whole convertion from the given vector form to the returned form is carried out by the reduce function. reduce takes three parameters as its arguments. The first parameter is a function "(fn [arr elem] (conj arr [:td elem]))". The second parameter is an optional value which is "[:tr]" in this case. The third param is a collection. The reduce function will call the given function with the value and first element of the collection, and will repeat this for every element in the collection. The first function is interesting because we never defined it. It is an anonymous function. You call an anonymous function with "fn". This anonymous function takes two prameters. A vector arr and a string elem from the row array. It then calls the "conj' function, which takes a collection as its first argument and adds the second argument to the collection. In this case the first argument is our initial value array, to which a new element array is added in every iteration.

Now we can generate one row of the table as required by the prxml functions. But tables seldom contain only one row. So we will create a gen-table function which will be called with an array of multiple rows.


(defn gen-row [row]
  (reduce
    (fn [arr elem]
      (conj
        arr
        [:td elem]))
    [:tr]
    row))
    
(defn gen-table[rows]
  (reduce
    (fn [arr row]
      (conj
        arr
        (gen-row row)))
    [:table]
    rows))


(println
  (gen-table
    [
      ["Bob" "bob@example.com" "29"]
      ["Bill" "bill@example.com" "32"]
    ]))

Now run this program and the output you will get will be for the whole table. We really dont need a separate gen-row function so we will add it to gen-table and shorten our program.
  
(defn gen-table[rows]
  (reduce
    (fn [arr row]
      (conj
        arr
        (reduce
          (fn [arr elem]
            (conj
              arr
              [:td elem]))
          [:tr]
          row)))
    [:table]
    rows))


(println
  (gen-table
    [
      ["Bob" "bob@example.com" "29"]
      ["Bill" "bill@example.com" "32"]
    ]))

Try this and you should get the same earlier output. The anonymous function fn has a simple form, in which you can simply write the function as #( ... ) and call the passed arguments as %1 %2 etc. So lets shorten our program even more.

(defn gen-table[rows]
  (reduce
    (fn [arr row]
      (conj
        arr
        (reduce
          #(conj
            %1
            [:td %2])
          [:tr]
          row)))
    [:table]
    rows))


(println
  (gen-table
    [
      ["Bob" "bob@example.com" "29"]
      ["Bill" "bill@example.com" "32"]
    ]))
  
So there you are, an even shorter program. Notice that I only used the short form, only for one of the anonymous functions, because nested short forms of the anonymous functions are not allowed.

Now let us put all this together with our html page generator of lesson 3 and see if we can actually see the table on a page.


(use 'clojure.contrib.prxml)


(defn gen-table[rows]
  (reduce
    (fn [arr row]
      (conj
        arr
        (reduce
          #(conj
            %1
            [:td %2])
          [:tr]
          row)))
    [:table {:border "2"}]
    rows))


(defn generate-page [filename title content]
  (spit
    filename
    (with-out-str
      (binding [*prxml-indent* 4]
        (prxml
          [:html
            [:body
              [:h1  {:style "color:red"} title]
              (gen-table content)]])))))


(generate-page
  "table.html"
  "An html page with a table"
  [
    ["Bob" "bob@example.com" "29"]
    ["Bill" "bill@example.com" "32"]
  ])

Now run the program and open the generated "table.html" in a browser window. Aren't you excited about the possibilities of Clojure? Then dont wait, on to lesson 5 - A Web Server in Clojure!

No comments:

Post a Comment