DEV Community

Cover image for Learning Clojure, part VII
Камило Куньа
Камило Куньа

Posted on

Learning Clojure, part VII

We learned how to use functions and some basic data structures, now we finally will start to make things a little more interesting as define values and functions.

Symbols

Symbols are a different kind of value, they are a name that represents a value. We can define symbols using the def function.

(def trainer "Green")
;; returns => #'user/trainer

(def team ["Pidgeotto" "Rattata" "Abra" "Charmander"])
;; returns => #'user/team
Enter fullscreen mode Exit fullscreen mode

When we evaluate values we get them as return, as an example:

"Red"
;; returns => "Red"

8
;; returns => 8
Enter fullscreen mode Exit fullscreen mode

But symbols evaluate for the value it's referring to:

trainer
;; returns => "Green"

team
;; returns => ["Pidgeotto" "Rattata" "Abra" "Charmander"]
Enter fullscreen mode Exit fullscreen mode

When we define symbols we're not binding it directly to a value, instead, it does it through a Var a mechanism that is used to refer to values.

When we defined our symbols trainer and team you should notice that it returned the name with #'user/ in front of it. It's saying we defined a var that's in the user namespace and that's because symbols are bound to a namespace and we can call them in the fully qualified name (we just don't have to do it when we're in the same namespace of the symbol):

user/trainer
;; returns => "Green"

user/team
;; returns => ["Pidgeotto" "Rattata" "Abra" "Charmander"]
Enter fullscreen mode Exit fullscreen mode

Is important that you have the attention that REPL always starts in the user namespace if you running these commands from a project maybe you have a different one declared at the beginning of the file. We can also redefine or define new namespaces as we see in the next parts.

let

Sometimes we want to define symbols that are just used locally instead of as global values in the namespace and we use the let function to do it, this function receives a vector with a pair number of elements ordered as symbols and values that are only available inside the same block (parenthesis) as let.

(let [region "Kanto"
      place  "Route 1"]
  (str "You are in " place " of the region " region))
;; returns => "You are in Route 1 of the region Kanto"

region
;; returns => Syntax error compiling at (REPL:0:0).
;;         => Unable to resolve symbol: region in this context

Enter fullscreen mode Exit fullscreen mode

We'll use let a lot when we're defining our functions. Defining local variables helps a lot in the legibility of our code since it helps to figure out what each value means.

Functions

As we have seen until this point we can evoke functions using forms (also called expressions) that have the syntax (operator arg arg args...), but it's time to learn how to define our functions.

Anonymous functions

Sometimes we just need a function that we need to pass as a parameter or just use briefly and they're the most basic use of functions, as they will be used just once we don't name them so they're now as anonymous functions.

We can define an anonymous function using the fn function that receives a vector with the parameters and then the function definition.

;; How define an anonymous function
(fn [x y] 
  (println "Making a vector...")
  [x y])
;; returns => #object[user$eval2048$fn__2049 0x6cce008e "user$eval2048$fn__2049@6cce008e"]

;; How to call an anonymous function
((fn [x y]
   (println "Making a vector...")
   [x y]) "The answer is " 42)
;; returns => Making a vector...
;;         => ["The answer is " 42]
Enter fullscreen mode Exit fullscreen mode

Clojure doesn't have any kind of instruction on what is the return of a function, instead, the last value of the body of the function is returned from the function (as the vector in our example above).

Functions always return some value in Clojure (even if it's a nil value).

Shorthand anonymous functions

Is possible to use # in front of the parenthesis to define an anonymous function.

(#(str "Hello " "Clojure!"))
;; returns => "Hello Clojure!"
Enter fullscreen mode Exit fullscreen mode

Is also possible to use % for shorthand as a parameter.

(#(str "Hello " %) "Camilo")
;; returns => "Hello Camilo"
Enter fullscreen mode Exit fullscreen mode

When the function expects multiple parameters is possible to number % as %1, %2...

(#(Math/pow %1 %2) 2 8)
;; returns => 256.0
Enter fullscreen mode Exit fullscreen mode

Naming functions

We can use the def function to bind an anonymous function (that is a value) to a symbol and it will help us to call them as many times we want intuitively.

(def sum-numbers (fn [& args] (reduce + args)))
;; returns => #'user/sum-numbers

(sum-numbers 7 7 7)
;; returns => 21

(sum-numbers 4 9 3 6 2 8)
;; returns => 32

(user/sum-numbers 14 39 43 -60 21 83)
;; returns => 140
Enter fullscreen mode Exit fullscreen mode

& is a special form in Clojure that is used to package the rest of the parameters in a vector. So in parameter definition [x y & z], this function will have 2 parameters x and y, and all other values passed after it will be in a vector called z. While in our case [& args] we packed all parameters in a vector called args.

Defining functions with defn

There's syntactic sugar for defining functions in a more readable way is the defn, you can think of it as (+ def fn) => defn. The defn gets as parameters a symbol, a vector of the function parameters, and the body of the function.

;; Function definition with defn
(defn name
  "Optional docstring that describes what this function does." 
  [params]
  ...params)

;; example
 (defn sum-evens [numbers]
   (->> numbers
        (filter even?)
        (reduce +)))
;; returns => #'user/sum-evens
Enter fullscreen mode Exit fullscreen mode

Functions with multiple arities

Arity is the number of arguments or operands taken by a function.

We can specify that a function can have multiple parameter possibilities, for this instead of passing a vector of parameters we will wrap each definition in a form where each receives a pair with a vector and the definition, for this work each definition has to be different from other.

(defn make-point
  ([x] [x 0])
  ([x y] [x y]))

(make-point 5)
;; returns => [5 0]

(make-point 5 4)
;; returns => [5 4]
Enter fullscreen mode Exit fullscreen mode

See you soon!

Now we're starting to have fun, with functions we can make a lot of useful things and I challenge you to start doing it. It's a good time to build simple functions that do things like flip a number order or a fizzbuzz exercise.

In the next chapters, we will continue to explore Clojure collections and learn how to use Hash-Maps to structure our data.

Top comments (1)

Collapse
 
magnus2025 profile image
Magnus Smith

nice intro series looking forward to next instalment