In the last post of our Clojure Journey series, we learned how to create functions, a powerful way of organize our code, dividing it in small pieces of logic. Just like wizards have their spell book with all kind of magics, we now have our functions, ready to help us solving our tasks.
Knowing how to create functions doesn’t mean that it’ll look beautiful, meaningful and readable, it takes time, but previous experience in others languages can help you in this journey to become a Clojure wizard.
To make your function more readable, in most of cases you’ll need to bind some information to an identifier, what we usually call “variables”, one of the way to do it in Clojure is using
def as we already learned before. As example, let’s imagine that we have a function that receives an recently created
order (just a simple map for sort of simplicity) and need to send to it’s buyer a confirmation email:
(defn send-confirmation-email [order] (send-email (:email (:user order)) (open-file "/example-path/template.html")))
This is the simplest way to do it, you get your information needed and pass it to the supposed
send-email. As a experienced wizard, you think that it can be more readable if you extract some informations and bind to a identifier to use it later, so you do it:
(defn send-confirmation-email [order] (def user-email (:email (:user order))) (def email-template (open-file "/example-path/template.html")) (send-email user-email email-template))
Good intentions, this code will work, but is not de best way to do it. It’s not a “Clojurist” way.
The reason behind this, is because
def will create an identifier associated with the current namespace (don’t worry, we already learned about namespaces), that can globally accessed, something usually called “global variables”, that can leads to a lot of problems when used in the wrong way, other parts of the code non related can start referring to it and create a dependency problem, or it be redefined and break your code.
Let the way to go
To solve this problem Clojure wizards introduced a new magic, called
let, using it you can bind value to a symbols in a lexical scope, in short, it creates something like a local variable for you, that will only live inside your function.
The syntax is simple to use this magic, just pass a vector containing N combinations of symbol and its binding.
=> (let [x 1 y 2] (+ x y)) 3
Pay attention to the lexical scope,
y will not exist outside let scope, so if you call them outside it, it will not exist.
let is good, but using it with functions is even better, and its real power shines, if we refactor our
send-confirmation-email using our new knowledge, it will look something like this:
(defn send-confirmation-email [order] (let [user-email (:email (:user order)) email-template (open-file "/example-path/template.html")] (send-email user-email email-template)))
Note: While using let, you can refer to variables defined above.
In this way, youll have your variables only in the function scope, without them being available out of the function scope. This is the Clojurist way of deal with local variables.
Let also allows us to use destructuring in the same way that we saw when we learned about it:
=> (def vec [:a :b :c :d]) #'user/vec => (let [[a b c d] vec] (println a b c d)) :a :b :c :d
Clojure also has if-let and when-let methods that can help you a lot with your code, I pretty recommend to read about it!
Let can make your functions more beautiful and correctly, dont forget to use it.
On the next post of this series, we’ll still talking about functions, this time talking about multi-arity functions.
If you have any questions that I can help you with, please ask! Send an email (otaviopvaladares at gmail.com), pm me on my Twitter, or comment on this post!