PCL -> Clojure, Chapter 5

This article is part of a series describing a port of the samples from Practical Common Lisp (PCL) to Clojure. You will probably want to read the intro first.

This article covers Chapter 5, Functions.

Passing arguments to functions

One of the cool things about Common Lisp is the variety of ways to pass arguments to functions. You can pass arguments

  • by position or by name
  • as a fixed or variable list
  • with preset defaults for some arguments

Clojure provides many of the same things, but the syntax is different. Here is a function that takes two required arguments, and two optional arguments.

  (defn foo [a b & [c d]]
    (list a b c d))

Here are some examples of calling foo:

  user=> (foo 1 2)
  (1 2 nil nil)

  user=> (foo 1 2 3 4)
  (1 2 3 4)

You can also pass values by name, using a map literal. The function bar expects named arguments a and b. The :or clause specifies a default for b:

  (defn bar [{:keys [a b] :or {b 10}}] (list a b))

Examples of calling bar:

  user=> (bar {:a 1})
  (1 10)
  
  user=> (bar {:a 1 :b 2})
  (1 2)   

Another nicety is defining an optional parameter's default value in terms of another parameter. Here is a Clojure approach that defaults height to width when creating a rectangle:

  (defn make-rectangle 
    ([width] (make-rectangle width width))
    ([width height] {:width width :height height}))

Usage:

  user=> (make-rectangle 20)
  {:width 20, :height 20}

  user=> (make-rectangle 10 20)
  {:width 10, :height 20}    

Corner cases

Common Lisp also supports discovering whether a user specified a parameter. This comes in handy if you want to detect that the user re-specified the default for some parameter. I didn't find a built-in way to do this in Clojure. Here is one approach:

  (defn which-args-supplied [{:keys [a b c] :as all :or {c 4}}]
    (let [c-supplied (contains? all :c)]
      (list a b c c-supplied)))

The :as :all collects the entire arguments list into all. Then, I use contains? to detect whether the user specified a value for c.

Usage:

  user=> (which-args-supplied {:a 1})
  (1 nil 4 false)

  user=> (which-args-supplied {:a 1 :c 4})
  (1 nil 4 true)

If you wanted to make this feel more like Common Lisp, you could write a macro.

Common Lisp also supports a return-from macro to "return" from the middle of a function. This encourages an imperative style of programming, which Clojure discourages.

However, you can solve the same problems in a different way. Here is the return-from example, rewritten in a functional style so that no return-from is needed:

  (defn pair-with-product-greater-than [n]
    (take 1 (for [i (range 10) j (range 10) :when (> (* i j) n)] [i j])))

Usage:

  user=> (pair-with-product-greater-than 30)
  ([4 8])
  
  user=> (pair-with-product-greater-than 50)
  ([6 9])

Dynamic invocation

To demonstrate funcall and apply, PCL uses a function to plot a histogram. Clojure provides an equivalent apply. The PCL version of plot also uses CL's loop. Instead, I will use doseq and dotimes for looping:

  (defn plot [f min max step]
    (doseq i (range min max step)
      (dotimes _ (apply f [i]) (print "*"))
      (println)))

The _ is idiomatic for "I don't plan to use this value". In this case I want to do something n times, but the individual iterations do not need to know their ordinal.

With plot in place, we can plot any function of one argument, over any range. (The usefulness of this is sharply limited by the horizontal resolution of your output device). Some examples:

  user=> (plot #(Math/pow % 2) 1 5 1)
  *
  ****
  *********
  ****************

  user=> (plot identity 2 10 2)
  **
  ****
  ******
  ********

Wrapping up

No big surprises here. Function invocation is flexible and powerful in Clojure.


Notes

The sample code is available at http://github.com/stuarthalloway/practical-cl-clojure.

Get In Touch