Thursday, September 30, 2010

Clojure: Another Testing Framework - Expectations

Once upon a time I wrote Expectations for Ruby. I wanted a simple testing framework that allowed me to specify my test with the least amount of code.

Now that I'm spending the majority of my time in Clojure, I decided to create a version of Expectations for Clojure.

At first it started as a learning project, but I kept adding productivity enhancements. Pretty soon, it became annoying when I wasn't using Expectations. Obviously, if you write your own framework you are going to prefer to use it. However, I think the productivity enhancements might be enough for other people to use it as well.

So why would you want to use it?

Tests run automatically. Clojure hates side effects, yeah, I hear you. But, I hate wasting time and repeating code. As a result, Expectations runs all the tests on JVM shutdown. This allows you to execute a single file to run all the tests in that file, without having to specify anything additional. There's also a hook you can call if you don't want the tests to automatically run. (If you are looking for an example, there's a JUnit runner that disables running tests on shutdown)

What to test is inferred from your "expected" value. An equality test is probably the most common test written. In Expectations, an equality test looks like the following example.
(expect 3 (+ 1 2))
That's simple enough, but what if you want to match a regex against a string? The following example does exactly that, and it uses the same syntax.
(expect #"foo" "afoobar")
Other common tests are verifying an exception is thrown or checking the type of an actual value. The following snippets test those two conditions.
(expect ArithmeticException (/ 12 0))

(expect String "foo")
Testing subsets of the actual value. Sometimes you want an exact match, but there are often times when you only care about a subset of the actual value. For example, you may want to test all the elements of a map except the time and id pairs (presumably because they are dynamic). The following tests show how you can verify that some key/value pairs are in a map, a element is in a set, or an element is in a list.
;; k/v pair in map. matches subset
(expect {:foo 1} (in {:foo 1 :cat 4}))

;; key in set
(expect :foo (in (conj #{:foo :bar} :cat)))

;; val in list
(expect :foo (in (conj [:bar] :foo)))
Double/NaN is annoying. (not= Double/NaN Double/NaN) ;=> true. I get it, conceptually. In practice, I don't want my tests failing because I can't compare two maps that happen to have Double/NaN as the value for a matching key. In fact, 100% of the time I want (= Double/NaN Double/NaN) ;=> true. And, yes, I can rewrite the test and use Double/isNaN. I can. But, I don't want to. Expectations allows me to pretend (= Double/NaN Double/NaN) ;=> true. It might hurt me in the future. I'll let you know. For now, I prefer to write concise tests that behave as "expected".

Try rewriting this and using Double/isNaN (it's not fun)
(expect
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}}
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})
Concise Java Object testing. Inevitably, I seem to end up with a few Java objects. I could write a bunch of different expect statements, but I opted for a syntax that allows me to check everything at once.
(given (java.util.ArrayList.)
(expect
.size 0
.isEmpty true))
Trimmed Stacktraces. I'm sure it's helpful to look through Clojure and Java's classes at times. However, I find the vast majority of the time the problem is in my code. Expectations trims many of the common classes that are from Clojure and Java, leaving much more signal than noise. Below is the stacktrace reported when running the failure examples from the Expectations codebase.
failure in (failure_examples.clj:8) : failure.failure-examples
raw: (expect 1 (one))
act-msg: exception in actual: (one)
threw: class java.lang.ArithmeticException-Divide by zero
failure.failure_examples$two__375 (failure_examples.clj:4)
failure.failure_examples$one__378 (failure_examples.clj:5)
failure.failure_examples$G__381__382$fn__387 (failure_examples.clj:8)
failure.failure_examples$G__381__382 (failure_examples.clj:8)
Every stacktrace line is from my code, where the problem lives.

Descriptive Error Messages. Expectations does it's best to give you all the important information when a failure does occur. The following failure shows what keys are missing from actual and expected as well is which values do not match.
running

(expect
{:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})

generates

failure in (failure_examples.clj:110) : failure.failure-examples
raw: (expect {:z 1, :a 9, :b {:c Double/NaN, :d 1, :e 2, :f {:g 10, :i 22}}} {:x 1, :a Double/NaN, :b {:c Double/NaN, :d 2, :e 4, :f {:g 11, :h 12}}})
result: {:z 1, :a 9, :b {:c NaN, :d 1, :e 2, :f {:g 10, :i 22}}} are not in {:x 1, :a NaN, :b {:c NaN, :d 2, :e 4, :f {:g 11, :h 12}}}
exp-msg: :x is in actual, but not in expected
:b {:f {:h is in actual, but not in expected
act-msg: :z is in expected, but not in actual
:b {:f {:i is in expected, but not in actual
message: :b {:e expected 2 but was 4
:b {:d expected 1 but was 2
:b {:f {:g expected 10 but was 11
:a expected 9 but was NaN
note: I know it's a bit hard to read, but I wanted to cover all the possible errors with one example. In practice you'll get a few messages that will tell you exactly what is wrong.

For example, the error tells you
:b {:f {:g expected 10 but was 11
With that data it's pretty easy to see the problem in
(expect
{:z 1 :a 9 :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
{:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})
Expectations also tells you, when comparing two lists:
  • if the lists are the same, but differ only in order
  • if the lists are the same, but one list has duplicates
  • if the lists are not the same, which list is larger

    JUnit integration. My project uses both Java and Clojure. I like running my tests in IntelliJ and I like TeamCity running my tests as part of the build. To accomplish this using Expectations all you need to do is create a java class similar to the example below.
    import expectations.junit.ExpectationsTestRunner;
    import org.junit.runner.RunWith;

    @RunWith(expectations.junit.ExpectationsTestRunner.class)
    public class FailureTest implements ExpectationsTestRunner.TestSource{

    public String testPath() {
    return "/path/to/the/root/folder/holding/your/tests";
    }
    }
    The Expectations Test Runner runs your Clojure tests in the same way that the Java tests run, including the green/red status icons and clickable links when things fail.

    Why wouldn't you use Expectations?

    Support. I'm using it to test my production code, but if I find errors I have to go fix them. You'll be in the same situation. I'll be happy to fix any bugs you find, but I might not have the time to get to it as soon as you send me email.

    If you're willing to live on the bleeding edge, feel free to give it a shot.
  • Wednesday, September 01, 2010

    Clojure: Mocking

    An introduction to clojure.test is easy, but it doesn't take long before you feel like you need a mocking framework. As far as I know, you have 3 options.
    1. Take a look at Midje. I haven't gone down this path, but it looks like the most mature option if you're looking for a sophisticated solution.

    2. Go simple. Let's take an example where you want to call a function that computes a value and sends a response to a gateway. Your first implementation looks like the code below. (destructuring explained)
      (defn withdraw [& {:keys [balance withdrawal account-number]}]
      (gateway/process {:balance (- balance withdrawal)
      :withdrawal withdrawal
      :account-number account-number}))
      No, it's not pure. That's not the point. Let's pretend that this impure function is the right design and focus on how we would test it.

      You can change the code a bit and pass in the gateway/process function as an argument. Once you've changed how the code works you can test it by passing identity as the function argument in your tests. The full example is below.
      (ns gateway)

      (defn process [m] (println m))

      (ns controller
      (:use clojure.test))

      (defn withdraw [f & {:keys [balance withdrawal account-number]}]
      (f {:balance (- balance withdrawal)
      :withdrawal withdrawal
      :account-number account-number}))

      (withdraw gateway/process :balance 100 :withdrawal 22 :account-number 4)
      ;; => {:balance 78, :withdrawal 22, :account-number 4}

      (deftest withdraw-test
      (is (= {:balance 78, :withdrawal 22, :account-number 4}
      (withdraw identity :balance 100 :withdrawal 22 :account-number 4))))

      (run-all-tests #"controller")
      If you run the previous example you will see the println output and the clojure.test output, verifying that our code is working as we expected. This simple solution of passing in your side effect function and using identity in your tests can often obviate any need for a mock.

    3. Solution 2 works well, but has the limitations that only one side-effecty function can be passed in and it's result must be used as the return value.

      Let's extend our example and say that we want to log a message if the withdrawal would cause insufficient funds. (Our gateway/process and log/write functions will simply println since this is only an example, but in production code their behavior would differ and both would be required)
      (ns gateway)

      (defn process [m] (println "gateway: " m))

      (ns log)

      (defn write [m] (println "log: " m))

      (ns controller
      (:use clojure.test))

      (defn withdraw [& {:keys [balance withdrawal account-number]}]
      (let [new-balance (- balance withdrawal)]
      (if (> 0 new-balance)
      (log/write "insufficient funds")
      (gateway/process {:balance new-balance
      :withdrawal withdrawal
      :account-number account-number}))))

      (withdraw :balance 100 :withdrawal 22 :account-number 4)
      ;; => gateway: {:balance 78, :withdrawal 22, :account-number 4}

      (withdraw :balance 100 :withdrawal 220 :account-number 4)
      ;; => log: insufficient funds
      Our new withdraw implementation calls two functions that have side effects. We could pass in both functions, but that solution doesn't seem to scale very well as the number of passed functions grows. Also, passing in multiple functions tends to clutter the signature and make it hard to remember what is the valid order for the arguments. Finally, if we need withdraw to always return a map showing the balance and withdrawal amount, there would be no easy solution for verifying the string sent to log/write.

      Given our implementation of withdraw, writing a test that verifies that gateway/process and log/write are called correctly looks like a job for a mock. However, thanks to Clojure's binding function, it's very easy to redefine both of those functions to capture values that can later be tested.

      The following code rebinds both gateway/process and log/write to partial functions that capture whatever is passed to them in an atom that can easily be verified directly in the test.
      (ns gateway)

      (defn process [m] (println "gateway: " m))

      (ns log)

      (defn write [m] (println "log: " m))

      (ns controller
      (:use clojure.test))

      (defn withdraw [& {:keys [balance withdrawal account-number]}]
      (let [new-balance (- balance withdrawal)]
      (if (> 0 new-balance)
      (log/write "insufficient funds")
      (gateway/process {:balance new-balance
      :withdrawal withdrawal
      :account-number account-number}))))

      (deftest withdraw-test1
      (let [result (atom nil)]
      (binding [gateway/process (partial reset! result)]
      (withdraw :balance 100 :withdrawal 22 :account-number 4)
      (is (= {:balance 78, :withdrawal 22, :account-number 4} @result)))))

      (deftest withdraw-test2
      (let [result (atom nil)]
      (binding [log/write (partial reset! result)]
      (withdraw :balance 100 :withdrawal 220 :account-number 4)
      (is (= "insufficient funds" @result)))))

      (run-all-tests #"controller")
    In general I use option 2 when I can get away with it, and option 3 where necessary. Option 3 adds enough additional code that I'd probably look into Midje quickly if I found myself writing a more than a few tests that way. However, I generally go out of my way to design pure functions, and I don't find myself needing either of these techniques very often.

    Monday, August 30, 2010

    Clojure: Using Sets and Maps as Functions

    Clojure sets and maps are functions.

    Since they are functions, you don't need functions to get values out of them. You can use the map or set as the example below shows.
    (#{1 2} 1)
    > 1

    ({:a 2 :b 3} :a)
    > 2
    That's nice, but it's not exactly game changing. However, when you use sets or maps with high order functions you can get a lot of power with a little code.

    For example, the following code removes all of the elements of a vector if the element is also in the set.
    (def banned #{"Steve" "Michael"})
    (def guest-list ["Brian" "Josh" "Steve"])

    (remove banned guest-list)
    > ("Brian" "Josh")
    I'm a big fan of using sets in the way described above, but I don't often find myself using maps in the same way. The following code works, but I rarely use maps as predicates.
    (def banned {"Steve" [] "Michael" []})
    (def guest-list ["Brian" "Josh" "Steve"])

    (remove banned guest-list)
    > ("Brian" "Josh")
    However, yesterday I needed to compare two maps and get the list of ids in the second map where the quantities didn't match the quantities in the first map. I started by using filter and defining a function that checks if the quantities are not equal. The following code shows solving the problem with that approach.
    ; key/value pairs representing order-id and order-quantity
    (def map1 {1 44 2 33})
    (def map2 {1 55 2 33})

    (defn not=quantities [[id qty]] (not= (map1 id) qty))
    (keys (filter not=quantities map2))
    > (1)
    However, since you can use maps as filter functions you can also solve the problem by merging the maps with not= and filtering by the result. The following code shows an example of merging and using the result as the predicate.
    ; key/value pairs representing order-id and order-quantity
    (def map1 {1 44 2 33})
    (def map2 {1 55 2 33})

    (filter (merge-with not= map1 map2) (keys map2))
    > (1)
    I don't often find myself using maps as predicates, but in certain cases it's exactly what I need.

    Wednesday, August 11, 2010

    clojure.test Introduction

    I'll admit it, the first thing I like to do when learning a new language is fire up a REPL. However, I'm usually ready for the next step after typing in a few numbers, strings and defining a function or two.

    What feels like centuries ago, Mike Clark wrote an article about using unit testing to learn a new language. Mike was ahead of his time. This blog entry should help you if you want to follow Mike's advice.

    Luckily, Clojure has built in support for simple testing. (I'm currently using Clojure 1.2, you can download it from clojure.org)

    Before we get started, let's make sure everything is working. Save a file with the following clojure in it and run* it with clojure.
    (ns clojure.test.example
    (:use clojure.test))

    (run-all-tests)
    If everything is okay, you should see something similar the following output.
    Testing clojure.walk

    Testing clojure.core

    (a bunch of other namespaces tested)

    Testing clojure.zip

    Ran 0 tests containing 0 assertions.
    0 failures, 0 errors.
    If you've gotten this far, you are all set to start writing your own tests. If you are having any trouble, I suggest logging into the #clojure IRC chat room on Freenode.net

    The syntax for defining tests is very simple. The following test verifies that 1 + 1 = 2. You'll want to add the test after the ns definition and before the (run-all-tests) in the file you just created.
    (deftest add-1-to-1
    (is (= 2 (+ 1 1))))
    Running the test should produce something similar to the following output.
    Testing clojure.walk

    Testing clojure.test.example

    (a bunch of other namespaces tested)

    Ran 1 tests containing 1 assertions.
    0 failures, 0 errors.
    We see all of the standard clojure namespaces; however, we see our namespace (clojure.test.example) in the results as well. The output at the bottom also tells us that 1 test with 1 assertion was executed.

    The following example shows testing a custom add function. (we will add additional tests from here, without ever deleting the old tests)
    (defn add [x y] (+ x y))

    (deftest add-x-to-y
    (is (= 5 (add 2 3))))
    If everything goes to plan, running your tests should now produce the following text towards the bottom of the output.
    Ran 2 tests containing 2 assertions.
    0 failures, 0 errors.
    At this point you might want to pass in a few different numbers to verify that add works as expected.
    (deftest add-x-to-y-a-few-times
    (is (= 5 (add 2 3)))
    (is (= 5 (add 1 4)))
    (is (= 5 (add 3 2))))
    Running the tests shows us our status
    Ran 3 tests containing 5 assertions.
    0 failures, 0 errors.
    This works perfectly fine; however, clojure.test also provides are for verifying several values.

    The following example tests the same conditions using are.
    (deftest add-x-to-y-a-using-are
    (are [x y] (= 5 (add x y))
    2 3
    1 4
    3 2))
    And, the unsurprising results.
    Ran 4 tests containing 8 assertions.
    That's a simple are; however, you can do whatever you need in the form. Let's grab the value out of a map as an additional example.
    (deftest grab-map-values-using-are
    (are [y z] (= y (:x z))
    2 {:x 2}
    1 {:x 1}
    3 {:x 3 :y 4}))
    Leaving us with
    Ran 5 tests containing 11 assertions.
    The is and are macros will be all that you need for 90% of all the tests you'll ever want to write. For additional assertions and more details you can check out the clojure.test documentation.

    Advanced Topics (very unnecessary to get started)

    I get annoyed with noise in my test results. Our results have been very noisy due to the namespace reporting. The run-all-tests function takes a regular expression (documented here). We can change our test running call to include a regular expression, as the following example shows.
    (run-all-tests #"clojure.test.example")
    Once we switch to providing a regular expression the results should be limited to the following output.

    Testing clojure.test.example

    Ran 5 tests containing 11 assertions.
    0 failures, 0 errors.
    This approach works fine for our current sample file; however, it seems like a better solution would be to stop reporting namespaces that do not contain any tests. The following snippet changes the report multimethod to ignore namespaces that don't contain any tests.
    (defmethod report :begin-test-ns [m]
    (with-test-out
    (when (some #(:test (meta %)) (vals (ns-interns (:ns m))))
    (println "\nTesting" (ns-name (:ns m))))))
    If you're just getting started, don't worry you don't need to understand what's going on in that snippet. I've copied the original report method and made it conditional by adding the code in bold. As a result, the namespace is only printed if it contains any tests.

    Now that our results are clean, let's talk about ways of getting those results.

    Adding calls to the run-all-tests function isn't a big deal when working with one namespace; however, you'll need to get clever when you want to run a suite of tests. I've been told that leiningen and Maven have tasks that allow you to run all the tests. You might want to start there. I don't currently use either one, and I'm lazy. I don't want to set up either, especially since all I want to do is run all my tests.

    It turns out it's very easy to add a shutdown hook in Java. So, as a simple solution, I run all my tests from the Java shutdown hook.
    (.addShutdownHook
    (Runtime/getRuntime)
    (proxy [Thread] []
    (run []
    (run-all-tests))))
    In general, I create a test_helper.clj with the following code.
    (ns test-helper
    (:use clojure.test))

    (defmethod report :begin-test-ns [m]
    (with-test-out
    (if (some #(:test (meta %)) (vals (ns-interns (:ns m))))
    (println "\nTesting" (ns-name (:ns m))))))

    (.addShutdownHook
    (Runtime/getRuntime)
    (proxy [Thread] []
    (run []
    (run-all-tests))))
    Once you've created a test_helper.clj you can use test-helper (just like you used clojure.test) (example below) and your tests will automatically be run on exit, and only namespaces with tests will be included in the output.

    It's worth noting that some clojure.contrib namespaces seem to include tests, so in practice I end up using a regular expression that ignores all namespaces beginning with "clojure"** when running all tests. With all of those ideas combined, I find I can execute all my tests or only the tests in the current namespace very easily.

    Below you can find all the code from this entry.

    clojure.test.example.clj
    (ns clojure.test.example
    (:use clojure.test test-helper))

    (deftest add-1-to-1
    (is (= 2 (+ 1 1))))

    (defn add [x y] (+ x y))

    (deftest add-x-to-y
    (is (= 5 (add 2 3))))

    (deftest add-x-to-y-a-few-times
    (is (= 5 (add 2 3)))
    (is (= 5 (add 1 4)))
    (is (= 5 (add 3 2))))

    (deftest add-x-to-y-a-using-are
    (are [x y] (= 5 (add x y))
    2 3
    1 4
    3 2))

    (deftest grab-map-values-using-are
    (are [y z] (= y (:x z))
    2 {:x 2}
    1 {:x 1}
    3 {:x 3 :y 4}))
    test_helper.clj
    (ns test-helper
    (:use clojure.test))

    (defmethod report :begin-test-ns [m]
    (with-test-out
    (if (some #(:test (meta %)) (vals (ns-interns (:ns m))))
    (println "\nTesting" (ns-name (:ns m))))))

    (.addShutdownHook
    (Runtime/getRuntime)
    (proxy [Thread] []
    (run []
    (run-all-tests))))

    * Running a clojure file should be as easy as: java -cp /path/to/clojure.jar clojure.main -i file.to.run.clj

    ** (run-all-tests #"[^(clojure)].*") ; careful though, now your clojure.test.example tests will be ignored. Don't let that confuse you.

    Tuesday, July 27, 2010

    Clojure: Destructuring

    In The Joy of Clojure (TJoC) destructuring is described as a mini-language within Clojure. It's not essential to learn this mini-language; however, as the authors of TJoC point out, destructuring facilitates concise, elegant code.

    What is destructuring?
    Clojure supports abstract structural binding, often called destructuring, in let binding lists, fn parameter lists, and any macro that expands into a let or fn. -- http://clojure.org/special_forms
    The simplest example of destructuring is assigning the values of a vector.
    user=> (def point [5 7])
    #'user/point

    user=> (let [[x y] point]
    (println "x:" x "y:" y))
    x: 5 y: 7
    note: I'm using let for my examples of destructuring; however, in practice I tend to use destructuring in function parameter lists at least as often, if not more often.

    I'll admit that I can't remember ever using destructuring like the first example, but it's a good starting point. A more realistic example is splitting a vector into a head and a tail. When defining a function with an arglist** you use an ampersand. The same is true in destructuring.
    user=> (def indexes [1 2 3])
    #'user/indexes

    user=> (let [[x & more] indexes]
    (println "x:" x "more:" more))
    x: 1 more: (2 3)
    It's also worth noting that you can bind the entire vector to a local using the :as directive.
    user=> (def indexes [1 2 3])
    #'user/indexes

    user=> (let [[x & more :as full-list] indexes]
    (println "x:" x "more:" more "full list:" full-list))
    x: 1 more: (2 3) full list: [1 2 3]
    Vector examples are the easiest; however, in practice I find myself using destructuring with maps far more often.

    Simple destructuring on a map is as easy as choosing a local name and providing the key.
    user=> (def point {:x 5 :y 7})
    #'user/point

    user=> (let [{the-x :x the-y :y} point]
    (println "x:" the-x "y:" the-y))
    x: 5 y: 7
    As the example shows, the values of :x and :y are bound to locals with the names the-x and the-y. In practice we would never prepend "the-" to our local names; however, using different names provides a bit of clarity for our first example. In production code you would be much more likely to want locals with the same name as the key. This works perfectly well, as the next example shows.
    user=> (def point {:x 5 :y 7})
    #'user/point

    user=> (let [{x :x y :y} point]
    (println "x:" x "y:" y))
    x: 5 y: 7
    While this works perfectly well, creating locals with the same name as the keys becomes tedious and annoying (especially when your keys are longer than one letter). Clojure anticipates this frustration and provides :keys directive that allows you to specify keys that you would like as locals with the same name.
    user=> (def point {:x 5 :y 7})
    #'user/point

    user=> (let [{:keys [x y]} point]
    (println "x:" x "y:" y))
    x: 5 y: 7
    There are a few directives that work while destructuring maps. The above example shows the use of :keys. In practice I end up using :keys the most; however, I've also used the :as directive while working with maps.

    The following example illustrates the use of an :as directive to bind a local with the entire map.
    user=> (def point {:x 5 :y 7})
    #'user/point

    user=> (let [{:keys [x y] :as the-point} point]
    (println "x:" x "y:" y "point:" the-point))
    x: 5 y: 7 point: {:x 5, :y 7}
    We've now seen the :as directive used for both vectors and maps. In both cases the local is always assigned to the entire expression that is being destructured.

    For completeness I'll document the :or directive; however, I must admit that I've never used it in practice. The :or directive is used to assign default values when the map being destructured doesn't contain a specified key.
    user=> (def point {:y 7})
    #'user/point

    user=> (let [{:keys [x y] :or {x 0 y 0}} point]
    (println "x:" x "y:" y))
    x: 0 y: 7
    Lastly, it's also worth noting that you can destructure nested maps, vectors and a combination of both.

    The following example destructures a nested map
    user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
    #'user/book

    user=> (let [{name :name {pages :pages isbn-10 :isbn-10} :details} book]
    (println "name:" name "pages:" pages "isbn-10:" isbn-10))
    name: SICP pages: 657 isbn-10: 0262011530
    As you would expect, you can also use directives while destructuring nested maps.
    user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
    #'user/book
    user=>
    user=> (let [{name :name {:keys [pages isbn-10]} :details} book]
    (println "name:" name "pages:" pages "isbn-10:" isbn-10))
    name: SICP pages: 657 isbn-10: 0262011530
    Destructuring nested vectors is also very straight-forward, as the following example illustrates
    user=> (def numbers [[1 2][3 4]])
    #'user/numbers

    user=> (let [[[a b][c d]] numbers]
    (println "a:" a "b:" b "c:" c "d:" d))
    a: 1 b: 2 c: 3 d: 4
    Since binding forms can be nested within one another arbitrarily, you can pull apart just about anything -- http://clojure.org/special_forms
    The following example destructures a map and a vector at the same time.
    user=> (def golfer {:name "Jim" :scores [3 5 4 5]})
    #'user/golfer

    user=> (let [{name :name [hole1 hole2] :scores} golfer]
    (println "name:" name "hole1:" hole1 "hole2:" hole2))
    name: Jim hole1: 3 hole2: 5
    The same example can be rewritten using a function definition to show the simplicity of using destructuring in parameter lists.
    user=> (defn print-status [{name :name [hole1 hole2] :scores}] 
    (println "name:" name "hole1:" hole1 "hole2:" hole2))
    #'user/print-status

    user=> (print-status {:name "Jim" :scores [3 5 4 5]})
    name: Jim hole1: 3 hole2: 5
    There are other (less used) directives and deeper explanations available on http://clojure.org/special_forms and in The Joy of Clojure. I recommend both.

    **(defn do-something [x y & more] ... )