Using and defining functions
In this recipe, we will review Clojure's function definitions:
- Defining simple functions
- Defining variadic functions
- Defining multiple arity functions
- Defining functions that specify arguments using a keyword
- Defining functions with a pre-condition and a post-condition
Getting ready
You only need REPL, as described in the first recipe in this chapter, and no additional libraries. Start REPL so that you can test the sample code immediately in this recipe.
How to do it...
Here, we will learn how to define functions using Clojure. Let's start with a simple function which returns Hello world
:
Defining simple functions
Let's start with a minimum function definition. Here is a minimal syntax of defn
:
(defn funtion-name [arg1 arg2 ...] expr-1 expr-2 .. expr-n )
defn
is a special form. The first argument is a function name and is followed by a vector of one or more arguments, then one or more expressions. The last expression is returned to the caller.
Here, we define a very simple function. The hello
function returns a Hello world
string:
(defn hello [s] (str "Hello world " s " !")) ;;=> #'living-clojure.core/hello (hello "Nico") ;;=> "Hello world Nico !" (hello "Makoto") ;;=> "Hello world Makoto !"
The next sample defines a simple adder function:
(defn simple-adder [x y] (+ x y) ) ;;=> #'living-clojure.core/simple-adder (simple-adder 2 3) ;;=> 5
Defining variadic functions
A variadic function allows a variable number of arguments. The next example defines another adder. It may have an arbitrary number of arguments:
(defn advanced-adder [x & rest] (apply + (conj rest x)) ) ;;=> #'living-clojure.core/advanced-adder (advanced-adder 1 2 3 4 5) ;;=> 15
Defining multiple arity functions
Here, we will introduce the multiple arity function. The following function defines a single argument function and a couple of argument functions with the same defn
:
(defn multi-arity-hello ([] (hello "you")) ([name] (str "Hello World " name " !"))) ;;=> #'living-clojure.core/multi-arty-hello (multi-arity-hello) ;;=> Hello World you ! (multi-arity-hello "Nico") ;;=> Hello World Nico !
Defining functions that specify arguments using a keyword
Sometimes, specifying a keyword is useful, since it is not necessary to remember the order of arguments.
The next example shows how to define such a function. The options are :product-name
, :price
, and :description
. The :or
expression supplies default values if any values in keys are omitted:
(defn make-product-1 [serial & {:keys [product-name price description] :or {product-name "" price nil description "no description !"} } ] {:serial-no serial :product-name product-name :price price :description description} ) ;;=> #'living-clojure.core/make-product-1 (defn make-product-2 [serial & {:keys [product-name price description] :or {:product-name "" :description "no description !"} } ] {:serial-no serial :product-name product-name :price price :description description} ) ;;=> #'living-clojure.core/make-product-2 (make-product-1 "0000-0011") ;;=> {:serial-no "0000-0011", :product-name "", :price nil, :description "no description !"} (make-product-2 "0000-0011") ;;=> {:serial-no "0000-0011", :product-name nil, :price nil, :description nil}
Defining functions with pre-condition and post-condition
Clojure can define functions with pre-condition and post-condition. In the following defn
, :pre
checks whether an argument is positive. :post
checks whether the result is smaller than 10:
(require '[clojure.math.numeric-tower :as math]) ;;=> nil (math/sqrt -10) ;;=> NaN (defn pre-and-post-sqrt [x] {:pre [(pos? x)] :post [(< % 10)]} (math/sqrt x)) ;;=> #'living-clojure.core/pre-and-post-sqrt (pre-and-post-sqrt 10) ;;=> 3.1622776601683795 (pre-and-post-sqrt -10) ;;=> AssertionError Assert failed: (pos? x) user/pre-and-post-sqrt (form-init2377591389478394456.clj:1) (pre-and-post-sqrt 120) AssertionError Assert failed: (< % 10) user/pre-and-post-sqrt (form-init2377591389478394456.clj:1)
Moreover, in this recipe, we will show a more complicated function. The make-triangle
function prints a triangle with a character. If this function is called without a :char
argument, it prints a triangle made of asterisks. If it is called with a :char
argument, it prints a triangle comprising characters specified by :char
:
(defn make-triangle [no & {:keys [char] :or {char "*"}}] (loop [x 1] (when (<= x no) (dotimes [n (- no x)] (print " ")) (dotimes [n (if (= x 1) 1 (dec (* x 2)))] (print char)) (print "\n") (recur (inc x)) ) ) ) (make-triangle 5) ;;=> * ;;=> *** ;;=> ***** ;;=> ******* ;;=> ********* ;;=> nil (make-triangle 6 :char "x")) ;;=> x ;;=> xxx ;;=> xxxxx ;;=> xxxxxxx ;;=> xxxxxxxxx ;;=> xxxxxxxxxxx
How it works...
We have already reviewed how to define functions and how to use them. You should understand how they work after reviewing the previous section.
To define functions using defn
is the same as vars
bind to functions by fn
as follows:
(defn pow-py-defn [x] (* x x)) ;;=> #'living-clojure/pow-py-defn (def pow-by-def (fn [x] (* x x))) ;;=> #'living-clojure/pow-by-def (pow-py-defn 10) ;;=> 100 (pow-by-def 10) ;;=> 100
There's more...
clojure.repl
has some useful functions to use with other functions. To get a symbol in the specific namespace, use clojure.repl/dir
:
(require 'clojure.string) ;;=> nil (clojure.repl/dir clojure.string) ;;=> blank? ;;=> capitalize ;;=> escape ;;=> join ;;=> lower-case ;;=> re-quote-replacement ;;=> replace ;;=> replace-first ;;=> reverse ;;=> split ;;=> split-lines ;;=> trim ;;=> trim-newline ;;=> triml ;;=> trimr ;;=> upper-case ;;=> nil
To get the documentation of a function, use clojure.repl/doc
:
(clojure.repl/doc clojure.string/trim) ------------------------- ;;=> clojure.string/trim ;;=> ([s]) ;;=> Removes whitespace from both ends of string. ;;=> nil
To get symbols that have a specific string, use clojure.repl/apropos
:
(clojure.repl/apropos "defn") ;;=> (clojure.core/defn ;;=> clojure.core/defn- ;;=> deps.compliment.v0v2v4.compliment.sources.local-bindings/defn-like-forms ;;=> deps.compliment.v0v2v4.deps.defprecated.v0v1v2.defprecated.core/defn)