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
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.
Here, we will learn how to define functions using Clojure. Let's start with a simple function which returns Hello world
:
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
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
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 !
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}
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
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
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)