camdez.com

Rule #1: There are no rules.

Callability in Clojure

| Comments

I’ve been hacking some Clojure code the last couple days and something I’m really coming to love is Clojure’s terse syntax for lambda functions.

The basic form is pretty darn short…

1
2
(map (fn [x] (* x x)) [1 2 3])
=> (1 4 9)

…but we can make that even shorter with this syntax that doesn’t even require us to name the argument:

1
2
(map #(* % %) [1 2 3])
=> (1 4 9)

…or, if we have multiple arguments:

1
2
(map #(str %1 " made " %2) ["McCarthy" "Matz" "Hickey"] ["Lisp" "Ruby" "Clojure"])
=> ("McCarthy made Lisp" "Matz made Ruby" "Hickey made Clojure")

Which makes a lot of sense if you think about it; we’re using a lambda function in the first place to avoid needlessly giving a function a name—why not extend that logic to the arguments? (Obviously don’t take that to extremes…)

This reminds me a bit of Ruby’s clever &:sym idiom, which makes it significantly more convenient to invoke methods on arrays of objects:

1
2
3
4
5
6
[1, 2, 3].map &:to_s
=> ["1", "2", "3"]

# Equivalent to:
[1, 2, 3].map {|n| n.to_s}
=> ["1", "2", "3"]

If, however, we’re dealing with the fairly common case of wanting to extract an array of values from an array of hashes, we’re back to the “long” form:

1
2
3
4
5
projects = [{name: "Lisp",    author: "McCarthy"},
            {name: "Ruby",    author: "Matz"},
            {name: "Clojure", author: "Hickey"}]
projects.map {|p| p[:name]}
=> ["Lisp", "Ruby", "Clojure"]

Functional (and quasi-functional) languages tend to have the terseness of the first case as a sort of freebie because they implement methods as functions over objects (or just use non-method functions for similar scenarios):

1
2
(map str [1 2 3])
=> ("1" "2" "3")

But Clojure does something really nifty in the second case where we were retrieving values from hashes. Clojure provides callability via an extensible abstraction called a Protocol, and Maps—what I’ve been loosely referring to as “hashes”—implement this protocol. This means that we can index into them as if we were making a function call:

1
2
3
4
5
(let [tweet {:id 178892625460461568
             :text "Goal 1: Infiltrate SXSW without a badge."
             :user {:name "dustin curtis" :screen_name "dcurtis"}}]
  (str (tweet :text) " --@" ((tweet :user) :screen_name)))
=> "Goal 1: Infiltrate SXSW without a badge. --@dcurtis"

Notice how the Map occupies the first position in list—the spot where we’d normally expect to see a function (or macro).

And that’s nice and all. But what’s really cool is that because keywords (e.g. :foo, :bar, :baz) are used so frequently as hash keys, they too implement the callability protocol:

1
2
3
4
5
6
7
8
(:name {:name "Lisp" :author "McCarthy"})
=> "Lisp"

(let [projects [{:name "Lisp"    :author "McCarthy"}
                {:name "Ruby"    :author "Matz"}
                {:name "Clojure" :author "Hickey"}]]
  (map :name projects))
=> ("Lisp" "Ruby" "Clojure")

Love it. Regardless of if I’m using hashes or objects it’s succinct and beautiful.

Now, extending callability is one of those black magics which should be used sparingly, but for educational purposes—EDUCATIONAL!—let’s dig into how such a thing is implemented. Let’s create a class to represent a shell command and make it directly callable, with the ability to pass command-line arguments.

The protocol we need to implement to make our class callable is called clojure.lang.IFn (I’m not entirely sure where that name comes from—my best guess is that ‘I’ is a prefix indicating interface and ‘Fn’ is to signify that this is the function calling interface). We’ll use defrecord to create a Java class for our callable objects. Here’s some (heavily-annotated) source code that should do the trick:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(use '[clojure.java.shell :only [sh]]) ; Bring in the function we need to execute shell commands

(defrecord ShellCommand [cmd]          ; Declare a class with an attribute to hold the command name
  clojure.lang.IFn                     ; Indicate that we're about to implement to callability protocol...
  (invoke [this] (this []))            ; A no-argument implementation of the protocol's method (invoke) which calls the single-argument version with an empty array
  (invoke [_ args]                     ; An implementation of invoke which expects arguments...
    ; Prepend the command to the arguments, execute, store in `results`
    (let [results (apply sh (conj (seq args) cmd))]
      (print (results :out))           ; Print the command's output
      (results :exit))))               ; Return the command's return value

(let [ls (ShellCommand. "ls")]         ; Create a ShellCommand object for the ls(1) command
  (ls)                                 ; Invoke ls like a function; no arguments
  (newline)                            ; Print a newline
  (ls ["-l"]))                         ; Invoke ls with an argument

Now let’s run it:

1
2
3
4
5
6
7
$ java -cp clojure-1.3.0/clojure-1.3.0.jar clojure.main callability.clj
callability.clj
post.md

total 16
-rw-r--r--  1 camdez  staff  1137 Mar 21 01:57 callability.clj
-rw-r--r--  1 camdez  staff  1652 Mar 20 12:21 post.md

And it works! Have any clever uses for making objects callable? Maybe some kind of (stateful) synchronization or remote-invokation constructs?

Comments