Clojure - Functions

Everything around functions

Multi-arity function can respond to different sets of arguments. Single-arity takes one set of arguments, multy-arity takes more than one set of arguments

;; multi-arity function , two argument sets
;; parameter expression has an extra ()
(defn greet
    ([person] (println "hello" person))            ; args set 1 'person'
    ([greeting person] (println greeting person))) ; args set 2 'greeting' and 'person'
(greet "vikram")           ; hello vikram
(greet "morning" "vikram") ; morning vikram

To reduce redundancy, the function can call itself when its called with fewer arguments

;; reduce redundancy
;; all call the one with maximum arity

(defn greet-reduce-redundancy
    ([person] (greet-reduce-redundancy "hello" person)) ; elf 
    ([greeting person] ( println greeting person)))

(greet-reduce-redundancy "vikram")           ; hello vikram
(greet-reduce-redundancy "morning" "vikram") ; morning vikram

Function that takes infinite arguments, uses &, called varargs or variadic functions, they have single argument set

Variadic function has

(defn pig[& args]               ; takes many arguments, the space after & is mandatory
   (println "Gobbled " args)    ; all arguments
   (println (first args)))      ; first argument 
(pig 1 2 3)    ; Gobbled (1 2 3), 1 

; get the first argument
(defn smart-pig [name & args]
    (println name))
(smart-pig 1 2 3 4) ; 1

Multimethod

A mechanism to implement dispatching of functions. Similar to RouteToLabel.

Our requirement is to take a message, determine its format, i.e., whether it’s xml, json, dfdl or none and call a function that implements the specific transformation logic to handle it.

First, decide what you want to call the common function. Here we’ll call it process-message.

We’ll define a special defmulti which maps this common method name to a special dispatcher which we call a router. The method name comes first and then the dispatcher function name next.

;; The multimethod maps the method name to the dispatcher
(defmulti process-message router)

Next, define a regular function that acts as a dispatcher which takes an argument and returns a label. Here, the router function looks at a message and figures out the format of the message, whether it’s xml, json, dfdl and returns a label that identifies the format. It has a catch all label of default.

;; Reads message, and dispatches to function that handles the format
(defn router [message]
    (cond
        (contains? message :dfdl) :dfdl-message       ; message has key dfdl 
        (contains? message :xml) :xml-message         ; message has key xml
        (= (message :label) :json) :json-message      ; message has key label with value json 
        :else :default ))                             ; catch all

Then define a method for each of the labels and the default label.

;; processes dfdl messages
(defmethod process-message :dfdl-message
  [message]
  {:destination "dfdl" :transformed-body (:payload message)})

;; processes xml messages
(defmethod process-message :xml-message
  [message]
  {:destination "xml" :transformed-body (:payload message)})

;; processes json messages
(defmethod process-message :json-message
  [message]
  {:destination "json" :transformed-body (:payload message)})

;; processes the rest of the messages
(defmethod process-message :default
  [message]
  {:destination "default" :transformed-body "Empty Body"})

Here is how you call it

;; Calling process-message
(process-message { :xml "a" :payload "<order></order>"})
(process-message { :label :json :payload "{order: "1234"}"})
(process-message { :dfdl "a" :payload "1234,4567"})
;; The output
{:destination xml, :transformed-body <order></order>}
{:destination json, :transformed-body {order: }
{:destination dfdl, :transformed-body 1234,4567}

Defining a new router function is not necessary. We just need a function that takes the same input and gives back a label.

Suppose the message is like this { :type :cobol :payload "old cobol"}.

Remember label is a function that returns its value in the collection.

That is, (:type message) gives back :cobol

So, the label can be used as the dispatch function.

Here’s a variation of the process-message called process-message-type.

The multifunction that takes the label :type instead of router as the dispatch function

;; use the :type instead of router
(defmulti process-message-type :type )

A corresponding implementation for the type :cobol

; process a message with :cobol type
(defmethod process-message-type :cobol [message]
    {:destination (:type message) :transformed-body "new cobol"})

Here’s the call

;; call
(println (process-message-type { :type :cobol :payload "old cobol"}))
;; output
{:destination :cobol, :transformed-body new cobol}