Chapter 3. Dipping our toes in the pool

 

 

Deeper and broader topics will be covered in later chapters, but now’s a good time to pick through an eclectic selection of smaller topics. The topics covered in this chapter stand alone but are important. Covering them now will be a fun way to start digging into practical matters of how to use Clojure.

We’ve covered a lot of conceptual ground in the previous chapter and built our Clojure lexicon. In this chapter, we’ll take a bit of a detour into some fundamental underpinnings driving idiomatic Clojure source code. First we’ll explore Clojure’s straightforward notions of Truthiness,[1] or the distinctions between values considered logical true and those considered logical false. Much of idiomatic Clojure code is built with matters of Truthiness in mind, and we’ll discuss Clojure’s extremely simple rules. After this we’ll then move on to the notion of nil punning, or treating an empty sequence as nil. Those of you coming from a background in Lisp may recognize the term, but Clojure handles nil punning differently. We’ll discuss the idioms related to nil punning in Clojure and their rationale. We’ll then cover destructuring—a powerful mechanism for pulling apart collection types and binding their constituent parts as individual values. Using destructuring within your own code can often lead to extremely concise and elegant solutions, and we’ll provide some examples to illustrate this. Finally, we’ll sit down and pair-program together to gain an appreciation for the power of Clojure’s Read-Eval-Print Loop (REPL).

1 As a deviation from the definition coined by Stephen Colbert in his television show The Colbert Report. Ours isn’t about matters of gut feeling but rather about matters of Clojure’s logical truth ideal.

3.1. Truthiness

Truthfulness may be an important virtue, but it doesn’t come up much in programming. On the other hand, Truthiness, or the matter of logical truth values in Clojure, is critical.

Clojure has one Boolean context: the test expression of the if form. Other forms that expect Booleans—and, or, when, and so forth—are macros built on top of if. It’s here that Truthiness matters.

3.1.1. What’s truth?

Every value looks like true to if, except for false and nil. That means that values which some languages treat as false—zero-length strings, empty lists, the number zero, and so on—are all treated as true in Clojure:

(if true :truthy :falsey)  ;=> :truthy
(if [] :truthy :falsey)    ;=> :truthy
(if nil :truthy :falsey)   ;=> :falsey
(if false :truthy :falsey) ;=> :falsey

This may feel uncomfortable to you, depending on your background. But because branches in a program’s logic are already one of the most likely places for complexity and bugs, Clojure has opted for a simple rule. There’s no need to check a class’s definition to see if it acts like “false” when you think it should (as is sometimes required in Python, for example). Every object is “true” all the time, unless it’s nil or false.

3.1.2. Don’t create Boolean objects

It’s possible to create an object that looks a lot like, but isn’t actually, false.

Java has left a landmine for you here, so take a moment to look at it so that you can step past it gingerly and get on with your life:

(def evil-false (Boolean. "false")) ; NEVER do this

This creates a new instance of Boolean—and that’s already wrong! Because there are only two possible values of Boolean, an instance of each has already been made for you—they’re named true and false.[2] But here you’ve gone and done it anyway, created a new instance of Boolean and stored it in a Var named evil-false. It looks like false:

2 Clojure’s true and false instances are the same as Java’s Boolean/TRUE and Boolean/FALSE, respectively.

evil-false
;=> false

Sometimes it even acts like false:

(= false evil-false)
;=> true

But once it gains your trust, it’ll show you just how wicked it is by acting like true:

(if evil-false :truthy :falsey)
;=> :truthy

Java’s own documentation warns against the creation of this evil thing, and now you’ve been warned again. If you just want to parse a string, use the Boolean class’s static valueOf method instead of its constructor. This is the right way:

(if (Boolean/valueOf "false") :truthy :falsey)
;=> :falsey

3.1.3. nil versus false

Rarely do you need to differentiate between the two false values, but if you do, you can use nil? and false?:

(when (nil? nil) "Actually nil, not false")
;=> "Actually nil, not false"

Keeping in mind the basic rule that everything in Clojure is truthy unless it’s false or nil is an astonishingly powerful concept, allowing for elegant solutions. Often programming languages have complicated semantics for Truthiness, but Clojure manages to avoid those matters nicely. You’ll see this simplicity leveraged throughout this book and in all examples of idiomatic Clojure source code.

Building on that theme, we’ll now discuss the matter of nil punning, which may or may not surprise you given your background.

3.2. Nil pun with care

Because empty collections act like true in Boolean contexts, we need an idiom for testing whether there’s anything in a collection to process. Thankfully, Clojure provides just such a technique:

(seq [1 2 3])
;=> (1 2 3)

(seq [])
;=> nil

The seq function returns a sequence view of a collection, or nil if the collection is empty. In a language like Common Lisp, an empty list acts as a false value and can be used as a pun (a term with the same behavior) for such in determining a looping termination. As you saw in section 2.3, Clojure’s empty sequences are instead truthy, and therefore to use one as a pun for falsity will lead to heartache and despair. One solution that might come to mind is to use empty? in the test, leading to the awkward phrase (when-not (empty? s) ...). Though it would work, this isn’t idiomatic. A better solution would be to use seq as a termination condition, as in the following function print-seq:

(defn print-seq [s]
  (when (seq s)
    (prn (first s))
    (recur (rest s))))

(print-seq [1 2])
; 1
; 2
;=> nil

(print-seq [])
;=> nil

There are a number of points to take away from this example. First, the use of seq as a terminating condition is the idiomatic way to test whether a sequence is empty. If we tested just s instead of (seq s), then the terminating condition wouldn’t occur even for empty collections, leading to an infinite loop.

 

Prefer Doseq

An important point not mentioned is that it would be best to use doseq in this case, but that wouldn’t allow us to illustrate our overarching points: the Clojure forms named with do at the start (doseq, dotimes, do, and so on) are intended for side-effects in their bodies and generally return nil as their results.

 

Second, rest is used to consume the sequence on the recursive call, which can return a sequence that’s either empty or not empty (has elements). Clojure also provides a next function that returns a seq of the rest, or (seq (rest s)), and thus never returns an empty sequence, but nil instead. But rest is appropriate here because we’re using seq explicitly in each subsequent iteration. Finally, print-seq is a template for most functions in Clojure, in that it shows that we should generally not assume seq has been called on our collection arguments, but instead call seq within the function itself and process based on its result. Using this approach fosters a more generic handling of collections, a topic that we explore in great detail in chapter 5. In the meantime, it’s important to keep in mind the difference between empty collections and false values; otherwise your attempts at nil punning may cause groans all around.

To top off our trifecta of core Clojure concepts, we next explore the most powerful of the three—destructuring. You’ll see just how powerful this mini-language within Clojure can be toward developing elegant and often beautiful solutions.

3.3. Destructuring

In the previous section, we briefly described Clojure’s destructuring facility as a mini-language embedded within Clojure. Destructuring allows us to positionally bind locals based on an expected form for a composite data structure. In this section, we’ll explore how destructuring can be used to pull apart composite structures into bindings through the lens of a simple rolodex example project.

 

Pattern Matching

Destructuring is loosely related to pattern matching found in Haskell, KRC, or Scala, but much more limited in scope. For more full-featured pattern matching in Clojure, consider using http://github.com/dcolthorp/matchure, which may in the future be included in contrib as clojure.core.match.

 

3.3.1. Your assignment, should you choose to accept it

You’ve heard that the rolodex project has been overdue, but now every developer assigned to it is out sick. The QA team is ready to go, but one function is still missing and it’s a show-stopper. You’re told to drop everything and write the function ASAP.

The design? Take a vector of length 3 that represents a person’s first, middle, and last names and return a string that will sort in the normal way, like “Steele, Guy Lewis”. What are you waiting for? Why aren’t you done yet?!?!

(def guys-whole-name ["Guy" "Lewis" "Steele"])

(str (nth guys-whole-name 2) ", "
     (nth guys-whole-name 0) " "
     (nth guys-whole-name 1)))
;=> "Steele, Guy Lewis"

Alas, by the time you’ve finished typing guys-whole-name for the fourth time, it’s too late. The customers have cancelled their orders, and the whole department is bound to be downsized.

If only you’d known about destructuring.

Okay, so you’re not likely to lose your job because your function is twice as many lines as it needs to be, but still that’s a lot of code repeated in a pretty small function. And using index numbers instead of named locals makes the purpose of the function more obscure than necessary.

Destructuring solves both these problems by allowing you to place a collection of names in a binding form where normally you’d put just a single name. One kind of binding form is the list of parameters given in a function definition.

3.3.2. Destructuring with a vector

So let’s try that again, but use destructuring with let to create more convenient locals for the parts of Guy’s name:

(let [[f-name m-name l-name] guys-whole-name]
  (str l-name ", " f-name " " m-name))

 

Positional destructuring

This positional destructuring doesn’t work on maps and sets because they’re not logically[3] aligned sequentially. Thankfully, positional destructuring will work with Java’s java.util.regex.Matcher and anything implementing the CharSequence and java.util.RandomAccess interfaces.

3 Technically, positional destructuring might make sense with sorted sets and maps, but alas it doesn’t operate as such.

 

This is the simplest form of destructuring, where you want to pick apart a sequential thing (a vector of strings in this case, though a list or other sequential collection would work as well), giving each item a name.

We don’t need it here, but we can also use an ampersand in a destructuring vector to indicate that any remaining values of the input should be collected into a (possibly lazy) seq:

(let [[a b c & more] (range 10)]
  (println "a b c are:" a b c)
  (println "more is:" more))
; a b c are: 0 1 2
; more is: (3 4 5 6 7 8 9)
;=> nil

Here the locals a, b, and c are created and bound to the first three values of the range. Because the next symbol is an ampersand, the remaining values are made available as a seq bound to more. The name more is pretty common for this purpose, but isn’t special—you’ll often see etc or xs instead, or some other name that makes sense in a particular context.

The final feature of vector destructuring is :as, which can be used to bind a local to the entire collection. It must be placed after the & local, if there is one, at the end of the destructuring vector:

(let [range-vec (vec (range 10))
      [a b c & more :as all] range-vec]
  (println "a b c are:" a b c)
  (println "more is:" more)
  (println "all is:" all))
; a b c are: 0 1 2
; more is: (3 4 5 6 7 8 9)
; all is: [0 1 2 3 4 5 6 7 8 9]
;=> nil

We made range-vec a vector in this example, and the directive :as binds the input collection as-is, entirely unmolested, so that the vector stays a vector. This is in contrast to &, which bound more to a seq, not a vector.

3.3.3. Destructuring with a map

Perhaps passing a name as a three-part vector wasn’t a good idea in the first place. It might be better stored in a map:

(def guys-name-map
  {:f-name "Guy" :m-name "Lewis" :l-name "Steele"})

But now we can’t use a vector to pick it apart effectively. Instead, we use a map:

(let [{f-name :f-name, m-name :m-name, l-name :l-name} guys-name-map]
  (str l-name ", " f-name " " m-name))

A couple things about this example may jump out at you. One might be that it still seems repetitive—we’ll get to that in a moment.

Another might be that it looks a bit unexpected to have the keywords like :f-name on the right-hand side of each pair even though the input map had keywords on the left. There are a couple reasons for that. The first is to help keep the pattern of the name on the left getting the value specified by the thing on the right. That is, the new local f-name gets the value looked up in the map by the key :f-name, just as the whole map gets its value from guys-name-map in the earlier def form.

The second reason is because it allows us to conjure up other destructuring features by using forms that would otherwise make no sense. Because the item on the left of each pair will be a new local name, it must be a symbol or possibly a nested destructuring form. But one thing it can’t be is a keyword, unless the keyword is a specially supported feature such as :keys, :strs, :syms, :as, and :or.

We’ll discuss the :keys feature first because it nicely handles the repetitiveness we mentioned earlier. It allows us to rewrite our solution like this:

(let [{:keys [f-name m-name l-name]} guys-name-map]
  (str l-name ", " f-name " " m-name))

So by using :keys instead of a binding form, we’re telling Clojure that the next form will be a vector of names that it should convert to keywords such as :f-name in order to look up their values in the input map. Similarly, if we had used :strs, Clojure would be looking for items in the map with string keys such as "f-name", and :syms would indicate symbol keys.

The directives :keys, :strs, :syms, and regular named bindings can appear in any combination and in any order. But sometimes you’ll want to get at the original map—in other words, the keys that you didn’t name individually by any of the methods just described. For that, you want :as, which works just like it does with vector destructuring:

(let [{f-name :f-name, :as whole-name} guys-name-map]
  whole-name)
;=> {:f-name "Guy", :m-name "Lewis", :l-name "Steele"}

If the destructuring map looks up a key that’s not in the source map, it’s normally bound to nil, but you can provide different defaults with :or:

(let [{:keys [title f-name m-name l-name], :or {title "Mr."}} guys-name-map]
  (println title f-name m-name l-name))
; Mr. Guy Lewis Steele
;=> nil
Associative Destructuring

One final technique worth mentioning is associative destructuring. Using a map to define a number of destructure bindings isn’t limited to maps. We can also destructure a vector by providing a map declaring the local name as indices into them, as shown:

(let [{first-thing 0, last-thing 3} [1 2 3 4]]
  [first-thing last-thing])
;=> [1 4]

We’ll explore associative destructuring later in section 6.1 when we discuss Clojure’s support for named structures. You’ve seen the shapes that destructuring takes within the let form, but you’re not limited to that exclusively, as we’ll explore next.

3.3.4. Destructuring in function parameters

All the preceding examples use let to do their destructuring, but exactly the same features are available in function parameters. Each function parameter can destructure a map or sequence:

(defn print-last-name [{:keys [l-name]}]
  (println l-name))

(print-last-name guys-name-map)
; Steele
;=> nil

Note that function arguments can include an ampersand as well, but this isn’t the same as destructuring. Instead, that’s part of their general support for multiple function bodies, each with its own number of parameters.

3.3.5. Destructuring versus accessor methods

In many object-oriented languages, you might create new classes to manage your application data objects, each with its own set of getter and setter methods. It’s idiomatic in Clojure to instead build your application objects by composing maps and vectors as necessary. This makes destructuring natural and straightforward. So anytime you find that you’re calling nth on the same collection a few times, or looking up constants in a map, or using first or next, consider using destructuring instead.

Now that we’ve made it through the cursory introduction to Clojure, let’s take some time to pair-program (Williams 2002). In the next section, we’ll take many of the bare necessities that you’ve just learned and walk through the creation of a couple interesting functions for drawing pretty pictures within Clojure’s REPL.

3.4. Using the REPL to experiment

Most software development projects include a stage where you’re not sure what needs to happen next. Perhaps you need to use a library or part of a library you’ve never touched before. Or perhaps you know what your input to a particular function will be, and what the output should be, but you aren’t sure how to get from one to other. In more static languages, this can be time-consuming and frustrating; but by leveraging the power of the Clojure REPL, the interactive command prompt, it can actually be fun.

3.4.1. Experimenting with seqs

Say someone suggests to you that coloring every pixel of a canvas with the xor of its x and y coordinates might produce an interesting image. It shouldn’t be too hard, so you can jump right in. You’ll need to perform an operation on every x and y in a pair of ranges. Do you know how range works?

(range 5)
;=> (0 1 2 3 4)

That should do nicely for one coordinate. To nest seqs, for often does the trick. But again, rather than writing code and waiting until you have enough to warrant compiling and testing, you can just try it:

(for [x (range 2) y (range 2)] [x y])
;=> ([0 0] [0 1] [1 0] [1 1])

There are the coordinates that will form your input. Now you need to xor them:

(xor 1 2)
;=> java.lang.Exception: Unable to resolve symbol: xor in this context

Bother—no function named xor. Fortunately, Clojure provides find-doc, which searches not just function names but also their doc strings for the given term:

(find-doc "xor")
; -------------------------
; clojure.core/bit-xor
; ([x y])
;   Bitwise exclusive or
;=> nil

So the function you need is called bit-xor:

(bit-xor 1 2)
;=> 3

Perfect! Now you want to adjust your earlier for form to return the bit-xor along with the x and y. The easiest way to do this will depend on what tool is hosting your REPL. In many, you can just press the up-arrow key on your keyboard a couple of times to bring back the earlier for form. You’re not going to want to retype things to make minor adjustments, so take a moment right now to figure out a method you like that will allow you to make a tweak like this by inserting the bit-xor call:

(for [x (range 2) y (range 2)] [x y (bit-xor x y)])
;=> ([0 0 0] [0 1 1] [1 0 1] [1 1 0])

That looks about right. Now you’re about to shift gears to pursue the graphics side of this problem, so tuck that bit of code away in a function so it’ll be easy to use later:

(defn xors [max-x max-y] (for [x (range max-x) y (range max-y)] [x y (bit-
     xor x y)]))
(xors 2 2)
;=> ([0 0 0] [0 1 1] [1 0 1] [1 1 0])

You might even save that into a .clj file somewhere, if you haven’t already.

3.4.2. Experimenting with graphics

Clojure’s REPL isn’t just for playing around; it’s also great for experimenting with Java libraries. We believe that there’s no better environment for exploring a Java API than Clojure’s REPL. To illustrate, poke around with java.awt, starting with a Frame:

(def frame (java.awt.Frame.))
;=> #'user/frame

That should’ve created a Frame, but no window appeared. Did it work at all?

frame
;=> #<Frame java.awt.Frame[frame0,0,22,0x0,invalid,hidden,...]>

Well, you have a Frame object, but perhaps the reason you can’t see it is hinted at by the word hidden in the #<Frame...> printout. Perhaps the Frame has a method you need to call to make it visible. One way to find out would be to check the Javadoc of the object, but because you’re at the REPL already, let’s try something else. You’ve already seen how the for macro works, so maybe you can check a class for which methods it has to see whether one that you can use is available:

(for [method (seq (.getMethods java.awt.Frame))
        :let [method-name (.getName method)]
        :when (re-find #"Vis" method-name)]
  method-name)
;=> ("setVisible" "isVisible")

The for macro takes a :let flag and bindings vector that works similarly to the let special form that you use to bind the local method-name to the result of calling the method .getName on each method in turn. The :when is used to limit the elements used in its body to only those that return a truthy value in the expression after the directive. Using these directives allows you to iterate through the methods and build a seq of those whose names match a regular expression #"Vis". We’ll cover Clojure’s regular expression syntax in section 3.5.

 

The Contrib Function Show

The clojure-contrib library also has a function show in the clojure.contrib.repl-utils namespace that allows for more useful printouts of class members than we show using for.

 

Your query returned two potential methods, so try out each of them:

(.isVisible frame)
;=> false

That’s false, as you might’ve suspected. Will setting it to true make any difference?

(.setVisible frame true)
;=> nil

It did, but it’s so tiny! Not to worry, as a Frame class also has a .setSize method that you can use:

(.setSize frame (java.awt.Dimension. 200 200))
;=> nil

And up pops a blank window for you to draw on. At this point, we’ll guide you through the rest of this section, but keep in mind that Java’s official API might be of interest should you choose to extend the example program.

 

The Javadoc Function

As of Clojure 1.2, a javadoc function is automatically available at the REPL to query and view official API documentation: (javadoc frame)

This should return a string corresponding to a URL and open a browser window for just the right page of documentation. Prior to Clojure 1.2, this function was in clojure.contrib.repl-utils.

 

What you need to draw into your Frame is its graphics context, which can be fetched as shown:

(def gfx (.getGraphics frame))
;=> #'user/gfx

Then, to actually draw, you can try out the fillRect method of that graphics context. If you’re trying this yourself, make sure the blank window is positioned so that it’s unobscured while you’re typing into your REPL:

(.fillRect gfx 100 100 50 75)

And just like that, you’re drawing on the screen interactively. You should see a single black rectangle in the formerly empty window. Exciting, isn’t it? You could be a kid playing with turtle graphics for the first time, it’s so much fun. But what it needs now is a dash of color:

(.setColor gfx (java.awt.Color. 255 128 0))
(.fillRect gfx 100 150 75 50)

Now there should be an orange rectangle as well. Perhaps the coloring would make Martha Stewart cry, but you now have tried out all the basic building blocks you’ll need to complete the original task: you have a function that returns a seq of coordinates and their xor values, you have a window you can draw into, and you know how to draw rectangles of different colors. Bear in mind that if you move the actual frame with the mouse, your beautiful graphics will disappear. This is just an artifact of this limited experiment and can be avoided using the full Java Swing capabilities.

3.4.3. Putting It All Together

What’s left to do? Use the graphics functions you just saw to draw the xor values you created earlier:

(doseq [[x y xor] (xors 200 200)]
  (.setColor gfx (java.awt.Color. xor xor xor))
  (.fillRect gfx x y 1 1))

The xors function you created earlier generates a seq of vectors, if you remember, where each vector has three elements: the x and y for your coordinates and the xor value that goes with them. The first line here uses destructuring to assign each of those three values to new locals x, y, and xor, respectively.

The second line sets the “pen” color to a gray level based on the xor value, and the final line draws a single-pixel rectangle at the current coordinates. The resulting graphic is shown in figure 3.1.

Figure 3.1. Visualization of xor. This is the graphic drawn by the six or so lines of code we’ve looked at so far—a visual representation of Clojure’s bit-xor function.

But just because you’ve succeeded doesn’t mean you have to quit. You’ve built up some knowledge and a bit of a toolbox, so why not play with it a little?

3.4.4. When things go wrong

For example, the pattern appears to cut off in the middle—perhaps you’d like to see a bit more. Re-enter that last expression, but this time try larger limits:

(doseq [[x y xor] (xors 500 500)]
  (.setColor gfx (java.awt.Color. xor xor xor))
  (.fillRect gfx x y 1 1))
; java.lang.IllegalArgumentException:
;    Color parameter outside of expected range: Red Green Blue

Whoops. Something went wrong, but what exactly? This gives you a perfect opportunity to try out one final REPL tool. When an exception is thrown from something you try at the REPL, the result is stored in a Var named *e. This allows you to get more detail about the expression, such as the stack trace:

(.printStackTrace *e)
; java.lang.IllegalArgumentException: Color parameter outside of
;         expected range: Red Green Blue
;     at clojure.lang.Compiler.eval(Compiler.java:4639)
;     at clojure.core$eval__5182.invoke(core.clj:1966)
;     at clojure.main$repl__7283$read_eval_print__7295.invoke(main.clj:180)
; ...skipping a bit here...
; Caused by: java.lang.IllegalArgumentException: Color parameter
;         outside of expected range: Red Green Blue
;     at java.awt.Color.testColorValueRange(Color.java:298)
;     at java.awt.Color.<init>(Color.java:382)
; ...skipping a bit more...
; ... 11 more
;=> nil

That’s a lot of text, but don’t panic. Learning to read Java stack traces will be useful, so let’s pick it apart.

The first thing to understand is the overall structure of the trace—there are two “causes.” The original or root cause of the exception is listed last—this is the best place to look first.[4] The name and text of the exception there are the same as the REPL printed for us in the first place, though they won’t always be. So let’s look at that next line:

4 This is a runtime exception, the most common kind. If you misuse a macro or find a bug in one, you may see compile-time exceptions. The trace will look similar but will have many more references to Compiler.java. For these traces, the most recent exception (listed first) may be the only one that identifies the filename and line number in your code that’s at fault.

at java.awt.Color.testColorValueRange(Color.java:298)

Like most lines in the stack trace, this has four parts: the name of the class, the name of the method, the filename, and finally the line number:

at <class>.<method or constructor>(<filename>:<line>)

In this case, the function name is testColorValueRange, which is defined in Java’s own Color.java file. Unless this means more to you than it does to us, let’s move on to the next line:

at java.awt.Color.<init>(Color.java:382)

It appears that it was the Color’s constructor (called <init> in stack traces) that called that test function you saw earlier. So now the picture is pretty clear—when you constructed a Color instance, it checked the values you passed in, decided they were invalid, and threw an appropriate exception.

If this weren’t enough, you could continue walking down the stack trace until the line

... 11 more

This is your cue to jump up to the cause listed before this one to find out what the next 11 stack frames were.

To fix your invalid Color argument, you can just adjust the xors function to return only legal values using the rem function, which returns the remainder so you can keep the results under 256:

(defn xors [xs ys]
  (for [x (range xs) y (range ys)]
    [x y (rem (bit-xor x y) 256)]))

Note that you’re redefining an existing function here. This is perfectly acceptable and well-supported behavior. Before moving on, create a function that takes a graphics object and clears it:

(defn clear [g] (.clearRect g 0 0 200 200))

Calling (clear gfx) will clear the frame, allowing the doseq form you tried before to work perfectly.

3.4.5. Just for fun

The bit-xor function does produce an interesting image, but perhaps you wonder what other functions might look like. Try adding another parameter to xors so that you can pass in whatever function you’d like to look at. Because it’s not just bit-xor anymore, change the name while you’re at it:

(defn f-values [f xs ys]
  (for [x (range xs) y (range ys)]
    [x y (rem (f x y) 256)]))

You might as well wrap your call to setSize, clear, and the doseq form in a function as well:

(defn draw-values [f xs ys]
  (clear gfx)
  (.setSize frame (java.awt.Dimension. xs ys))
  (doseq [[x y v] (f-values f xs ys)]
    (.setColor gfx (java.awt.Color. v v v))
    (.fillRect gfx x y 1 1)))

This allows you to try out different functions and ranges quite easily. More nice examples are shown in figure 3.2, resulting from the following:

(draw-values bit-and 256 256)
(draw-values + 256 256)
(draw-values * 256 256)
Figure 3.2. Three possible results from draw-values. The draw-values function you’ve written can be used to create a variety of graphics. Here are examples, from left to right, of bit-and, +, and *.

If this were the beginning or some awkward middle stage of a large project, you’d have succeeded in pushing past this troubling point and could now take the functions you’ve built and drop them into the larger project.

By trying everything out at the REPL, you’re encouraged to try smaller pieces rather than larger ones. The smaller the piece, the shorter the distance down an incorrect path you’re likely to go. Not only does this reduce the overall development time, but it provides developers more frequent successes that can help keep morale and motivation high through even tough stages of a project. But trial-and-error exploration isn’t enough. An intuitive basis in Clojure is also needed to become highly effective. Throughout this book, we’ll help you to build your intuition in Clojure through discussions of its idioms and its motivating factors and rationale.

3.5. Summary

We started slowly in this chapter in order to take a breather from the sprint that was chapter 2. Truthiness in Clojure observes a simple rule: every object is true all the time, unless it’s nil or false. Second, in many Lisp-like languages, the empty list () and the truth value nil are analogous—this is known as nil-punning—but in Clojure this isn’t the case. Instead, idiomatic Clojure employs the (seq (rest _)) idiom in the form of the next function to provide a mechanism fostering “form follows function” and also to eliminate errors associated with falsety/empty-seq disparity. Finally, destructuring provides a powerful mechanism, a mini-language for binding if you will, for partially or entirely pulling apart the constituent components of composite types. Our trek through the REPL illustrated the power in having the whole language (Graham 2001) at your disposal. As a Clojure programmer, you’ll spend a lot of time in the REPL, and pretty soon you won’t know how you lived without it.

In the next chapter, we’ll touch on matters concerning Clojure’s seemingly innocent scalar data types. Although in most cases these scalars will expose powerful programming techniques, be forewarned: as you’ll see, the picture isn’t always rosy.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.119.229