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 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
When we evaluate values we get them as return, as an example:
"Red" ;; returns => "Red" 8 ;; returns => 8
But symbols evaluate for the value it's referring to:
trainer ;; returns => "Green" team ;; returns => ["Pidgeotto" "Rattata" "Abra" "Charmander"]
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
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"]
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.
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
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.
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.
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]
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).
Is possible to use # in front of the parenthesis to define an anonymous function.
(#(str "Hello " "Clojure!")) ;; returns => "Hello Clojure!"
Is also possible to use % for shorthand as a parameter.
(#(str "Hello " %) "Camilo") ;; returns => "Hello Camilo"
When the function expects multiple parameters is possible to number % as %1, %2...
(#(Math/pow %1 %2) 2 8) ;; returns => 256.0
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
& 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.
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
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]
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.