Clojure Programming Cookbook
上QQ阅读APP看书,第一时间看更新

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)