Local Bindings

We have seen many examples of symbols that point to vars, including a few that we created ourselves:

(def chars (vec "42 0.5 1/2 foo-bar")) (def digits #{\1 \2 \3 \4 \5 \6 \7 \8 \9 \0})

You might be tempted to think that symbols always end up being vars, which contributes to the ongoing confusion about the difference between symbols and vars that we covered earlier.

Remember, vars are for pointing to global values. We've already come across an example of a symbol that is not a var. Recall the digit? function we made:

(defn digit? [ch] (contains? digits ch))

The ch is a symbol we created to represent the argument passed to the function. Is it globally accessible? Can I see what ch is anywhere outside of the defn above? No, it is only available inside the body of that function. Therefore function arguments are not vars; they are a separate thing.

There is another way to make symbols point to values temporarily, rather than globally, and that is let. Much like function arguments, symbols that are defined in a let only point to values while inside the let itself:

(let [first-char (get chars 0)] (digit? first-char))

Earlier, we made first-char a var with (def first-char (get chars 0)). The advantage to using let instead is that we avoid "polluting" our program with global values when we only need them temporarily.

Keep in mind that you can define as many local symbols as you want in a let. For example, this checks whether the first two characters are digits:

(let [first-char (get chars 0) second-char (get chars 1)] (and (digit? first-char) (digit? second-char)))

The previous example could just as easily be written without a let:

(and (digit? (get chars 0)) (digit? (get chars 1)))

However, by using a let, we can assign meaningful names to temporary values, rather than making heavily-nested code where one function call is embedded inside another. So, even if you find you can write code that way, using a let may make the code more readable.

Previous: Hash MapsNext: Loops