Functions

Create New Function Complementing Other Function

The Clojure function complement can be used to created a new function that returns the opposite truth value of the old function. The new function accepts the same number of arguments as the old function. Also when we invoke the new function created by the complement the old function is actually invoked and the result is used as argument for the not function to return the opposite truth value. So if the original function returns false or nil the result for the new function is true.

In the following example code we create a new function bad-weather that is the complement of good-weather:

(ns mrhaki.core.complement
  (:require [clojure.test :refer [is]]))

;; List with some temperatures for dates.
(def weather [{:date #inst "2020-06-05" :temperature 21}
              {:date #inst "2020-06-06" :temperature 17}
              {:date #inst "2020-06-07" :temperature 19}
              {:date #inst "2020-06-08" :temperature 25}
              {:date #inst "2020-06-09" :temperature 26}])

(defn good-weather
  "'Good' weather is when the temperature is between 20 and 30 degrees Celcius."
  [{temp :temperature}]
  (< 20 temp 30))

;; The opposite can easily be turned into a 
;; new function with complement. 
;; The bad-weather takes the same argument (temperature)
;; and returns false when good-weather would return true
;; and true when good-weather returns false.
(def bad-weather (complement good-weather))

(is (= [{:date #inst "2020-06-05" :temperature 21}
        {:date #inst "2020-06-08" :temperature 25}
        {:date #inst "2020-06-09" :temperature 26}]
       (filter good-weather weather)))

(is (= [{:date #inst "2020-06-06" :temperature 17}
        {:date #inst "2020-06-07" :temperature 19}]
       (filter bad-weather weather)))

;; Filtering on the good-weather and complement bad-weather
;; should return the same number of items as in the original collection.
(is (= 5
       (count weather)
       (count (concat (filter good-weather weather) (filter bad-weather weather)))))

Written with Clojure 1.10.1.

Original post written on June 15, 2020

Repeating A Value Or Function Invocation

In Clojure we can use the repeat function to get an infinite sequence of a given value. We can pass a length argument to get a fixed size sequence of the value. Clojure also provides the repeatedly function that takes as argument a function without arguments. A infinite sequence of invocations of the function is returned. Just like with the repeat function we can pass a length argument so the returned sequence has a fixed size.

We use the repeat and repeatedly function in the following example:

(ns mrhaki.core.repeat
  (:require [clojure.test :refer [is]])
  (:import (java.time LocalTime)))


;; repeat creates an infinite sequence of the given value.
(is (= ["Clojure" "Clojure" "Clojure"] 
       (take 3 (repeat "Clojure"))))

;; We can pass a length argument to restrict the number of 
;; times the value is repeated.
(is (= ["rocks" "rocks" "rocks"]
       (repeat 3 "rocks")))

(defn parrot-talk 
  [s]
  (repeat 2 s))

(is (= ["Polly wants a cracker" "Polly wants a cracker"]
       (parrot-talk "Polly wants a cracker")))


(defn before?
  "Helper function returns true if t1 is before t2, false otherwise"
  [[^LocalTime t1 ^LocalTime t2]]
  (.isBefore t1 t2))

(defn current-time 
  "Return current time"
  []
  (LocalTime/now))

;; repeatedly create an infinite sequence of function invocations.
;; The function must have no arguments.
(is (before? (take 2 (repeatedly current-time))))

(is (before? (repeatedly 2 current-time)))


(defn latest-time
  "Get the 'latest' time from a collection with time values."
  [coll]
  (reduce (fn [latest time] (if (.isAfter latest time) latest time)) coll))

(def current-times (repeatedly 100 current-time))

;; as each time is later than the next time value
;; the following equation must be true.
(is (= (latest-time current-times) (last current-times)))

Written with Clojure 1.10.1.

Original post written on June 8, 2020

Composing Functions With comp

In Clojure we can use the comp function to create a new function based on multiple other functions. We create a new function by composing other functions. We can invoke the function that is returned from the comp function with input arguments. These arguments are applied to the right most function that was used with comp. Then the output of that function is the input for the next function.

In the following example code we see several usages of the comp function. Also we see how the ordening of the functions can change the output:

(ns mrhaki.core.comp
  (:require [clojure.test :refer [is]]
            [clojure.string :as str]))

;; Some functions that work with numbers.
(defn f [x] (+ 11 (- x 90)))
(defn g [x] (* 2 x))

;; Use comp to create a new function based
;; on functions f and g.
;; Function are applied from right to left,
;; so first f and then g.
(is (= 42 ((comp g f) 100)))

;; Notice ordering is important.
;; In this case first g, then f is applied.
(is (= 121 ((comp f g) 100)))


;; User data to work with.
(def user {:name "Hubert" :alias "mrhaki"})

;; Simple function to repeat the value twice.
(def duplicate (partial repeat 2))

;; Compose new function from functions
;; :alias (keyword function), 
;; str/capitalize and
;; duplicate.
;; Function are applied from right to left.
(is (= '("Mrhaki" "Mrhaki")
       ((comp duplicate str/capitalize :alias) user)))

;; Other alternatives to chaining functions
(is (= '("Mrhaki" "Mrhaki")
       ;; using an anonymous function
       (#(duplicate (str/capitalize (:alias %1))) user)
       ;; or thread macro
       (-> user :alias str/capitalize duplicate)
       ;; or simply nested functions.
       (duplicate (str/capitalize (:alias user))))) 

Written with Clojure 1.10.1.

Original post written on May 20, 2020

Set Default Value For nil Function Argument With fnil

The function fnil can be used to create a new function from an existing function where we define default values for arguments that can be nil. Especially when we want to use a function that we didn't write ourselves this can be helpful to handle nil values in a consistent way. We can prevent for example a NullPointerException by setting a default value. We can define default values for a maximum of three arguments for the original function. When we invoke the function that is returned from the fnil function any extra arguments after the first arguments are simply passed on to the original function.

In the following example code we define a new times function based on the * function. In the example we define a default value for the first three arguments.

(ns mrhaki.core.fnil
  (:require [clojure.test :refer [is]]))

;; + throws NullPointerException when argument is nil.
(is (thrown? NullPointerException (+ nil 1)))

;; Create new function with default 0 when argument is nil.
(is (= 1 ((fnil + 0) nil 1)))


(def times 
  "Return function * with 
  default value 1 for argument 1 when nil,
  default value 10 for argumen 2 when nil and
  default value 100 for argument 3 when nil."
  (fnil * 1 10 100))

(is (= 2 
       (times nil 2)
       (* 1 2)))

(is (= 20 
       (times 2 nil) 
       (* 2 10)))

(is (= 2000 
       (times 2 nil nil) 
       (* 2 10 100)))

(is (= 200
       (times nil 2 nil)
       (* 1 2 100)))

(is (= 20
       (times nil nil 2)
       (* 1 10 2)))

;; We can use more arguments that are passed
;; to the original * function.
(is (= 4000 
       (times 2 nil nil 2)
       (* 2 10 100 2)))

(is (= 1000
       (times nil nil nil)
       (* 1 10 100)))

(is (= 2000
       (times nil nil nil 2)
       (* 1 10 100 2)))

Written with Clojure 1.10.1.

Original post written on May 12, 2020

Getting Results From Multiple Functions With Juxt Function

The function juxt can be used to get results from multiple functions on the same argument in one go. We pass the functions we want to invoke as arguments to the juxt function. This results in a new function that will return a vector with the results from each original function that is passed to the juxt function. So the first element in the result vector is the result of the first function, the second element is the result of the second function and so on. The documentation of the juxt function shows this clearly as ((juxt a b c)) => [a(x) b(x) c(x)].

In the following example we use the juxt function to apply multiple functions on a string, collection and map:

(ns mrhaki.core.juxt
  (:require [clojure.test :refer [is]]))

;; The functions first, last and count are applied
;; to the string value and we get a vector
;; with the result of each function.
(is (= [\C \e 18]
       ((juxt first last count) "Clojure is awesome")))


(defn sum 
  "Calculate sum of values in collection"
  [coll] 
  (apply + coll))

(defn avg 
  "Calculate average of values in collection"
  [coll] 
  (/ (sum coll) (count coll)))

;; Create new function summarize to give back statistics
;; on a collection based on count, min, max, sum and average.
(def summarize (juxt count #(apply min %) #(apply max %) sum avg))

(is (= [4 1 108 171 171/4]
       (summarize [1 20 42 108])))

(let [[count min max sum avg] (summarize [1 20 42 108])]
  (is (= 4 count))
  (is (= 1 min))
  (is (= 108 max))
  (is (= 171 sum))
  (is (= 171/4 avg)))


;; As keywords are functions we can use them to get values
;; for keys in a map.
(is (= ["Hubert" "mrhaki"]
       ((juxt :name :alias) {:alias "mrhaki" :name "Hubert" :location "Tilburg"})))

Written with Clojure 1.10.1.

Original post written on April 17, 2020

Pure Function Sample Buying Coffee From FP Programming In Scala Written In Clojure

Some of our colleagues at JDriven work with Scala and we talked about the book Functional Programming in Scala written by Paul Chiusano and Runar Bjarnason. We looked at one of the examples in the first chapter in the book to show the importance of having pure functions without side effects. The example is about buying a cup of coffee and charging a credit card with the purchase. In three examples a function with side effects is refactored to a pure function without side effects. When looking at the example I was wondering how this would look like in Clojure using only functions and simple immutable data structures. We can look at the examples in Scala to see how it is explained and implemented in the book. There are also Kotlin samples available. In this post we see a possible implementation in Clojure to buy coffee and charge our credit card.

The first example is a buy-coffee function with a credit card type as parameter. When we invoke the function with a credit card argument the credit card gets charged as side effect and a coffee type is created and returned. The coffee type is simply a map with a price key.

(ns mrhaki.coffee.cafe1)

(defn charge
  "Charge credit card with given amount.
   This function has side effects with a println in our example,
   but in a real world implementation could also include
   REST API calls, database access or other side effects."
  [cc amount]
  (println (str "Charge " amount " to credit card " (:number cc))))

(defn buy-coffee
  "Sample function to buy a coffee and charge the given credit card
   with the price of the coffee.
   The function returns coffee."
  [cc]
  (let [cup {:price 2.99}]
    (charge cc (:price cup))
    cup))

Let’s write a simple test for the buy-coffee function:

(ns mrhaki.coffee.cafe1-test
  (:require [clojure.test :refer :all]
            [mrhaki.coffee.cafe1 :refer :all]))

(deftest buy-coffee-test
  (is (= {:price 2.99} (buy-coffee {:number "123-456"})))
  (is (= {:price 2.99} (buy-coffee {:number "456-789"}))))

The test runs and shows the function works as expected, but we cannot test the side effect. If the side effect would involve calls to other systems, those systems would be called when we run our tests. That is something we would try to avoid, because it makes testing in isolation more difficult.

In the Scala example the next step is to pass in a Payment type that will take care of charging the credit card. This would make testing the function easier, because we can pass a testable implementation of the Payment type (e.g. a mock). So at least in our tests we can have an implementation that doesn’t access external systems.

For our Clojure example we can pass a payment function as argument to the buy-coffee function. This function takes a credit card type and amount as arguments and will be invoked in the buy-coffee function.

(ns mrhaki.coffee.cafe2)

(defn buy-coffee
  "Sample function to buy coffee and charge the given credit card
   with the price of the coffee.
   The function to charge the credit card is given as second argument.
   The fn-payment function must support a credit card as first
   argument and an amount as second argument."
  [cc fn-payment]
  (let [cup {:price 2.99}]
    (fn-payment cc (:price cup))
    cup))

When we write a test we can now check how the payment function is invoked:

(ns mrhaki.coffee.cafe2-test
  (:require [clojure.test :refer :all]
            [mrhaki.coffee.cafe2 :refer :all]))

(deftest buy-coffee-test
  (let [payment (fn [cc amount]
                  (is (= "123-456" (:number cc)))
                  (is (= 2.99 amount)))]
    (is (= {:price 2.99}
           (buy-coffee {:number "123-456"} payment)))))

But our buy-coffee function is not pure. We have still the side effect, but we made the function better testable. It is also mentioned in Functional Programming in Scala that if we want to invoke our buy-coffee function multiple times, the credit card also gets charged multiple times and this would include extra processing fees as well.

The final implementation of the buy-coffee will no longer charge the credit card, but it will a charge type with all information needed for charging the credit card, together with returning the coffee type. In another function we can handle the actual charging of the credit card by invoking REST calls, database calls or what else is needed. But now it is no longer a concern of our buy-coffee function. Also we can now invoke the buy-coffee function multiple times and combine all returned charge types into a single charge, to save on processing fees.

In our Clojure example we change the buy-coffee function and return both a coffee type and a charge type. The charge type is a map with a key for the credit card and a key for the amount to be charged. Also we introduce the buy-coffees function to show how we can invoke buy-coffee multiple times and combine the charges into a single charge.

(ns mrhaki.coffee.cafe3)

(defn buy-coffee
  "Sample function to buy coffee.
   The function returns a vector with coffee as first element and
   a charge type as second element."
  [cc]
  (let [cup {:price 2.99}]
    [cup {:credit-card cc :amount (:price cup)}]))

(defn- combine
  "Return a new charge with the sum of the amount values when
   the credit card number keys are equal.
   Throws exception when credit card numbers are not equal."
  [charge1 charge2]
  (if (= (:number charge1) (:number charge2))
    (update-in charge1 [:amount] + (:amount charge2))
    (throw (Exception. "Can't combine charges to different cards."))))

(defn- unzip
  "Returns pair of collections where first collection is built from first
   values of each pair in the coll argument and the second collection
   is built from the second value of each pair."
  [coll]
  (reduce (fn [[coll-a coll-b] [a b]]
            [(conj coll-a a) (conj coll-b b)])
          [[] []]
          coll))

(defn buy-coffees
  "Buy multiple times a cofee and combine all charges for given credit card.
  The first parameter accepts a credit card type and
  the second parameter is the number of times a coffee is bought."
  [cc n]
  (let [[coffees charges] (unzip (repeatedly n #(buy-coffee cc)))]
    [coffees (reduce combine charges)]))

And testing the functions is now much easier:

(ns mrhaki.coffee.cafe3-test
  (:require [clojure.test :refer :all]
            [mrhaki.coffee.cafe3 :refer :all]))

(deftest buy-coffee-test
  (let [[coffee charge] (buy-coffee {:number "123-456"})]
    (is (= {:price 2.99} coffee))
    (is (= {:credit-card {:number "123-456"} :amount 2.99} charge))))

(deftest buy-coffees-test
  (let [[coffees charge] (buy-coffees {:number "123-456"} 2)]
    (is (= (repeat 2 {:price 2.99}) coffees))
    (is (= {:credit-card {:number "123-456"} :amount 5.98} charge))))

The charge type also makes it easier to work with the charges. For example we can add a coalesce function that takes a collection of charge types and returns all charges per credit card. We can use this information to minimize the processing fees with the credit card company.

(ns mrhaki.coffee.cafe3)

...

(defn coalesce
  "Coalesce same card charges."
  [charges]
  (->> charges
       (group-by :credit-card)
       (vals)
       (map #(reduce combine %))))

And we can write the following test:

(ns mrhaki.coffee.cafe3-test
  (:require [clojure.test :refer :all]
            [mrhaki.coffee.cafe3 :refer :all]))
...

(deftest coalesce-test
  (let [charge-1a {:credit-card {:number "123-456"} :amount 2.99}
        charge-2a {:credit-card {:number "456-789"} :amount 2.99}
        charge-1b {:credit-card {:number "123-456"} :amount 2.99}
        charges (coalesce [charge-1a charge-2a charge-1b])]
    (is (= {:credit-card {:number "123-456"} :amount 5.98} (first charges)))
    (is (= {:credit-card {:number "456-789"} :amount 2.99} (second charges)))))

In the examples we use a map as type for the coffee, credit card and charge types. We can add records for these types to our Clojure examples to have some more semantics in our code. The good thing is that a records still can be used queries as a map, so a keyword function like :price still works for a Coffee record.

In the next example we add records and use them in the buy-coffee function. Notice the other functions still work without changes.

(ns mrhaki.coffee.cafe4)

;; Define records.
(defrecord Coffee [price])
(defrecord CreditCard [number])
(defrecord Charge [credit-card amount])

(defn buy-coffee
  "Sample function to buy coffee.
   The function returns a vector with coffee as first element and
   a charge type as second element."
  [cc]
  (let [cup (->Coffee 2.99)
        charge (->Charge cc (:price cup))]
    [cup charge]))

(defn- combine
  "Return a new charge with the sum of the amount values when
   the credit card number keys are equal.
   Throws exception when credit card numbers are not equal."
  [charge1 charge2]
  (if (= (:number charge1) (:number charge2))
    (update-in charge1 [:amount] + (:amount charge2))
    (throw (Exception. "Can't combine charges to different cards."))))

(defn- unzip
  "Returns pair of collections where first collection is built from first
   values of each pair in the coll argument and the second collection
   is built from the second value of each pair."
  [coll]
  (reduce (fn [[coll-a coll-b] [a b]]
            [(conj coll-a a) (conj coll-b b)])
          [[] []]
          coll))

(defn buy-coffees
  "Buy multiple times a cofee and combine all charges for given credit card.
  The first parameter accepts a credit card type and
  the second parameter is the number of times a coffee is bought."
  [cc n]
  (let [[coffees charges] (unzip (repeatedly n #(buy-coffee cc)))]
    [coffees (reduce combine charges)]))

(defn coalesce
  "Coalesce same card charges."
  [charges]
  (->> charges
       (group-by :credit-card)
       (vals)
       (map #(reduce combine %))))

In our tests we can now also use the functions to create a Coffee, CreditCard and Charge records:

(ns mrhaki.coffee.cafe4-test
  (:require [clojure.test :refer :all]
            [mrhaki.coffee.cafe4 :refer :all]))

(deftest buy-coffee-test
  (let [[coffee charge] (buy-coffee (->CreditCard "123-456"))]
    (is (= (->Coffee 2.99) coffee))
    (is (= (->Charge (->CreditCard "123-456") 2.99)) charge)))

(deftest buy-coffees-test
  (let [[coffees charge] (buy-coffees (->CreditCard "123-456") 2)]
    (is (= (repeat 2 (->Coffee 2.99)) coffees))
    (is (= (->Charge (->CreditCard "123-456") 5.98)) charge)))

(deftest coalesce-test
  (let [cc1     (->CreditCard "123-456")
        cc2     (->CreditCard "456-789")
        charges (coalesce [(->Charge cc1 2.99)
                           (->Charge cc2 2.99)
                           (->Charge cc1 2.99)])]
    (is (= (->Charge (->CreditCard "123-456") 5.98)) (first charges))
    (is (= (->Charge (->CreditCard "456-789") 2.99)) (second charges))))

Written with Clojure 1.10.1

Original post written on March 8, 2021