Search

Dark theme | Light theme
Showing posts with label Clojure:Goodness. Show all posts
Showing posts with label Clojure:Goodness. Show all posts

May 21, 2024

Clojure Goodness: Extending is Macro With Custom Assertions

The is macro in the clojure.test namespace can be used to write assertions about the code we want to test. Usually we provide a predicate function as argument to the is macro. The prediction function will call our code under test and return a boolean value. If the value is true the assertion passes, if it is false the assertion fails. But we can also provide a custom assertion function to the is macro. In the clojure.test package there are already some customer assertions like thrown? and instance?. The assertions are implemented by defining a method for the assert-expr multimethod that is used by the is macro. The assert-expr multimethod is defined in the clojure.test namespace. In our own code base we can define new methods for the assert-expr multimethod and provide our own custom assertions. This can be useful to make tests more readable and we can use a language in our tests that is close to the domain or naming we use in our code.

The implementation of the custom assertion should call the function do-report with a map containing the keys :type, :message, :expected and :actual. The :type key can have the values :fail or :pass. Based on the code we write in our assertion we can set the value correctly. Mostly the :message key will have the value of the message that is defined with the is macro in our tests. The keys :expected and :actual should contain reference to what the assertion expected and the actual result. This can be a technical reference, but we can also make it a human readable reference.

In the following example we implement a new customer assertion jedi? that checks if a given name is a Jedi name. The example is based on an example that can be found in the AssertJ documentation.

(ns mrhaki.test
  (:require [clojure.test :refer [deftest is are assert-expr]]))

(defmethod assert-expr 'jedi?
  "Assert that a given name is a Jedi."
  [msg form]
  `(let [;; We get the name that is the second element in the form.
         ;; The first element is the symbol `'jedi?`.
         name# ~(nth form 1)
         ;; We check if the name is part of a given set of Jedi names.
         result# (#{"Yoda" "Luke" "Obiwan"} name#)
         ;; We create an expected value that is used in the assertion message.
         expected# (str name# " to be a jedi.")]
     (if result#
       (do-report {:type     :pass
                   :message  ~msg,
                   :expected expected#
                   :actual   (str name# " is actually a jedi.")})
       (do-report {:type     :fail
                   :message  ~msg,
                   :expected expected#
                   :actual   (str name# " is NOT a jedi.")}))
     result#))

;; We can use our custom assertion in our tests.
(deftest jedi
  (is (jedi? "Yoda")))

;; The custom assertion can also be used with
;; the are macro as it will expand into multiple
;; is macro calls.
(deftest multiple-jedi
  (are [name] (jedi? name)
    "Yoda" "Luke" "Obiwan"))

;; The following test will fail, so we can
;; see failure message with the :expected and :actual values.
(deftest fail-jedi
  (is (jedi? "R2D2") "Is it?"))

If we run our failing test we see in the output that the assertion message is using our definition of the expected and actual values:

...
 expected: "R2D2 to be a jedi."
   actual: "R2D2 is NOT a jedi."
...

Written with Clojure 1.11.3.

May 19, 2024

Clojure Goodness: Combine Multiple Test Cases With are

The clojure.test namespace has the are macro that allows us to combine multiple test cases for the code we want to test, without having to write multiple assertions. We can provide multiple values for a function we want to test together with the expected values. Then then macro will expand this to multiple expressions with the is macro where the real assertion happens. Besides providing the data we must also provide the predicate where we assert our code under test. There is a downside of the are macro and that is that in case of assertion failures the line numbers in the error message could be off.

The first argument of the are macro is a vector with symbols that represent the names of the variables we want to use in the predicate. The second argument is the predicate where we write our assertion for the code under test. The remaining arguments are values for the symbols in the first argument. We can provide multiple sets of values for the symbols in the first argument, and the are macro will use this when the macro is expanded into multiple expressions.

In the next example we use the are macro to test the full-name function.

(ns mrhaki.test
  (:require [clojure.test :refer [deftest are]]
            [clojure.string :as str]))

;; Function we want to test.
(defn is-palidrome?
  [s]
  (= s (str/reverse s)))

(deftest palidrome
  (are
   ;; Vector with symbol used in test expression
   [s]
   ;; Test expression where we test the is-palidrome? function
   (true? (is-palidrome? s))
    ;; Data for the input symbol s
    "radar"
    "kayak"
    "racecar"
    "madam"
    "refer"
    "step on no pets"))

In the following example we use a test expression where we also use an expected value that is provided with the input data:

(ns mrhaki.test
  (:require [clojure.test :refer [deftest are]]))

;; Function we want to test.
(defn full-name
  "Returns a full name"
  [first-name last-name]
  (str first-name " " last-name))

(deftest sample
  (are
   [first-name last-name result]
   (= (full-name first-name last-name) result)
    "Fat" "Tony" "Fat Tony"
    "Krusty" "the Clown" "Krusty the Clown"))

Written with Clojure 1.11.3.

October 30, 2022

Clojure Goodness: Getting But The Last Element(s) From A Collection

When we want to get elements from a collection, but not the last element(s), we can use the function butlast and drop-last. The function butlast returns all elements before the last element. If we use the function on an empty collection we get back a nil result. When we use the function drop-last we get back a lazy sequence. Also we can use an extra argument for the function to indicate how many of the last elements we don't want in the result. If we use drop-last on an emtpy collection we get back an empty lazy sequence.

In the following example we use both functions with several collections:

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

;; Sample vector with some JVM langauges.
(def languages ["Clojure" "Groovy" "Java"])

;; Sample map describing a user.
(def user {:alias "mrhaki" :first "Hubert" :last "Klein Ikkink" :country "The Netherlands"})

;; Using butlast to get all elements but 
;; not the last element as a sequence.
(is (= '("Clojure" "Groovy") (butlast languages)))

;; We can also use butlast on a map, the result
;; is a sequence with vectors containing the 
;; key/value pairs from the original map.
(is (= '([:alias "mrhaki"] [:first "Hubert"] [:last "Klein Ikkink"])
       (butlast user))
;; We can use the into function to transform this
;; into a map again.
    (= {:alias "mrhaki" :first "Hubert" :last "Klein Ikkink"}
       (into {} (butlast user))))

;; Returns nil when collection is empty.
(is (= nil (butlast [])))


;; drop-last returns a lazy sequence with all 
;; elements but the last element.
(is (= '("Clojure" "Groovy") (drop-last languages)))

;; Returns an empty sequence when collection is empty.
(is (= '() (drop-last [])))

;; We can use an extra argument with but-last
;; to indicate the number of items to drop
;; from the end of the collection.
;; butlast cannot do this.
(is (= ["Clojure"] (drop-last 2 languages)))

;; drop-last works on maps just like butlast.
(is (= '([:alias "mrhaki"]) (drop-last 3 user))
    (= {:alias "mrhaki"} (into {} (drop-last 3 user))))

Written with Clojure 1.11.1.

September 27, 2022

Clojure Goodness: Reading Text File Content With slurp

The slurp funtion in Clojure can be used to read the contents of a file and return it as a string value. We can use several types as argument for the function. For example a string argument is used as URI and if that is not valid as a file name of the file to read. A File instance can be used directly as argument as well. But also Reader, BufferedReader, InputStream, URI, URL, Socket, byte[] and char[]. As an option we can specify the encoding used to read the file content using the :encoding keyword. The default encoding is UTF-8 if we don't specify the encoding option.

In the following example we use the slurp function in different use cases. We use a file named README with the content Clojure rocks!:

(ns mrhaki.sample.slurp
    (:require [clojure.java.io :as io]
              [clojure.test :refer [is]])
    (:import (java.io File)))
  
  ;; slurp interperts a String value as a file name.
  (is (= "Clojure rocks!" (slurp "files/README")))
  ;; Using the encoding option.
  (is (= "Clojure rocks!" (slurp "files/README" :encoding "UTF-8")))

  ;; We can also use an explicit File object.
  (is (= "Clojure rocks!" (slurp (io/file "files/README"))))
  (is (= "Clojure rocks!" (slurp (File. "files/README"))))
  
  ;; We can also use an URL as argument.
  ;; For example to read from the classpath:
  (is (= "Clojure rocks!" (slurp (io/resource "data/README"))))
  
  ;; Or HTTP endpoint
  (is (= "Clojure rocks!" (slurp "https://www.mrhaki.com/clojure.txt")))

Written with Clojure 1.11.1.

September 27, 2021

Clojure Goodness: Create All Parent Directories For A File

The Clojure namespace clojure.java.io contains useful functions to work with files. One of those functions is make-parents. We can pass a java.io.File instance as arguments or use the same arguments that can be passed to the file function that is also in this namespace. The function will create all parent directories for the file. The return result is true if the directories are created (they didn't exist before) and false when the directories didn't have to be created (already exist).

In the following example we see an example of usage of the make-parents function:

(ns mrhaki.io.make-parents
  (:require [clojure.java.io :refer [make-parents file]]
            [clojure.test :refer [is]]))

;; make-parents will create the parents directories for a file 
;; The function returns true if the directories are created, 
;; false if the directories already exist.
(let [file (file "tmp" "clojure" "sample.txt")]
  (is (true? (make-parents file)) "All parent directories are created")
  (is (false? (make-parents file)) "Second time the directory already exists"))

;; make-parents uses the same arguments as the clojure.java.io.file function
(is (true? (make-parents "tmp" "clj" "sample.txt")) "Directories tmp/clj are created")

Written with Clojure 1.10.3.

March 22, 2021

Clojure Goodness: Turning Map Values To Map Keys

In the clojure.set namespace we can find the function map-invert. This function returns a new map where the values are keys with the appropriates keys of the original map assigned as value. If the original map has duplicate values than the latest key for the duplicate value will be the value of the new key.

In the following example code we see the result of using map-invert:

(ns mrhaki.set.map-invert
  (:require [clojure.set :refer [map-invert]]
            [clojure.test :refer [is]]))

(is (= {"mrhaki" :alias "Clojure" :language}
       (map-invert {:alias "mrhaki" :language "Clojure"})))

;; With duplicate values only one will be key.
(is (= {1 :c 2 :b 3 :d}
       (map-invert {:a 1 :b 2 :c 1 :d 3})))

Written with Clojure 1.10.1.

March 18, 2021

Clojure Goodness: Create New Instance Of Java Class

Working with Java classes from Clojure code is easy. If we want to invoke methods or access fields on instances of Java classes we must first create an instance by invoking the constructor. In Clojure we can do that using the special form new or using a dot (.) notation. The new special form has the class name of the Java class we want to create an instance of as argument followed by arguments for the Java class constructor. With the dot notation we place a . after the class name followed by arguments for the class constructor to create a new instance.

In the following example we see several ways to create an instance of a Java class.

(ns mrhaki.java.new-instance
  (:require [clojure.test :refer [is]])
  (:import (java.net URI)
           (java.util Map TreeMap)))

;; Using a dot after the class name to invoke the constructor.
(is (instance? String (String.)))

;; Or using the new special form to invoke the constructor.
(is (instance? String (new String)))

;; Constructor arguments can be used.
(is (instance? URI (URI. "https://www.mrhaki.com")))
(is (instance? URI (new URI "https" "www.mrhaki.com" "/" "")))

;; We can use Clojure data structures in constructors.
(is (instance? Map (TreeMap. {:language "Clojure"})))

Written with Clojure 1.10.1.

March 8, 2021

Clojure Goodness: 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

February 23, 2021

Clojure Goodness: Merge Maps With Function To Set Value Duplicate Keys

In Clojure we can use the merge function to merge multiple maps into a single map. If a key is in multiple maps the value of the key merged last will be used in the resulting map. If we want to influence how the value of a duplicate key is set we can use merge-with. We specify as first argument the function that will be used when the same key is available in multiple maps. The function must accept two arguments, where the the first argument is the value of the key in the first map and the second argument the value of the same key in the following map. The result is assigned to the key in the resulting map. If we pass more than two maps to the merge-with the function will be called multiple times for a key if it is part of more than two maps.

In the following example we use Clojure core functions and a custom function to merge multiples maps, so we can alter the value for duplicate keys:

(ns mrhaki.core.merge-with
  (:require [clojure.test :refer [is]]))

;; Merge maps and use the function specified as first argument
;; to calculate the value for keys that are present
;; in multiple maps.
(is (= {:a 60 :b 3 :c 44 :d 100}
       (merge-with * {:a 2 :b 3 :c 4} {:a 10 :c 11} {:a 3 :d 100})))

;; Works for all maps and independent of type that is used for keys.
;; We can use any function for merge-with.
(def languages (merge-with (comp vec flatten conj) {"Clojure" [:dynamic :functional]}
                           {"Java" [:jvm]}
                           {"Groovy" [:jvm]}
                           {"Clojure" [:jvm]}
                           {"Groovy" [:dynamic]}))

(is (= {"Clojure" [:dynamic :functional :jvm]
        "Java"    [:jvm]
        "Groovy"  [:jvm :dynamic]}
       languages))


;; Sample map with small inventory.
(def inventory {"pencil" {:count 10 :price 0.25}
                "pen"    {:count 23 :price 0.4}})
;; Sample basket with items.
(def basket {"pencil" {:count 5} "pen" {:count 2}})

;; Function to subtract the :count value for a basket item
;; from the :count value for the same inventory item.
(defn item-sold
  [inventory-item basket-item]
  (update-in inventory-item [:count] - (:count basket-item)))

(is (= {"pencil" {:count 5 :price 0.25}
        "pen"    {:count 21 :price 0.4}}
       (merge-with item-sold inventory basket)))

Written with Clojure 1.10.1.

February 9, 2021

Clojure Goodness: Destructure Sequences

Clojure supports advanced destructure features. In a previous post we learned about destructuring maps, but we can also destructure vectors, list and sequences in Clojure using positional destructuring. We can define symbols for positions in the sequence to assign the value at a certain position to the symbol. The first symbol in the destructure vector gets the value of the first element in the sequence, the second symbol the value of the second element and so on. To get the remaining elements from the sequence without assigning them to specific symbols we can use & followed by a symbol. Then all remaining elements are assigned as sequence the symbol. Finally we can use :as to get the original vector, list or sequence.

The folowing examples show several destructure definitions for different type of collections and sequences:

(ns mrhaki.lang.destruct-seq
  (:require [clojure.test :refer [is]]))

(def items ["mrhaki" "Hubert Klein Ikkink" "Tilburg"])

;; Elements from the items vector are positionally
;; destructured to symbols.
(let [[alias name city] items]
  (is (= "mrhaki" alias))
  (is (= "Hubert Klein Ikkink" name))
  (is (= "Tilburg" city)))

;; When we define a symbol but there are no elements
;; to assign a value, the symbol will be nil.
(let [[alias name city country] items]
  (is (nil? country)))

;; When we don't need the destructured symbol we can
;; use the underscore to indicate this. But any name will do.
(let [[username _ city] items]
  (is (= "mrhaki lives in Tilburg" 
         (str username " lives in " city))))

;; We can destructure sequences just like vectors.
(def coords '(29.20090, 12.90391))

(let [[x y] coords]
  (is (= 29.20090 x))
  (is (= 12.90391 y)))

(let [[first-letter _ third-letter] "mrhaki"]
  (is (= \m first-letter))
  (is (= \h third-letter)))


;; We can nest our destructure definitions.
(def currencies [[42 "EUR"] [50 "USD"]])

;; We want the second value of the first element and
;; the first value of the second element.
(let [[[_ currency] [amount _]] currencies]
  (is (= "EUR" currency))
  (is (= 50 amount)))

;; Example sequence with fruit names.
(def basket '("Apple" "Pear" "Banana" "Grapes" "Lemon"))

;; We can use & to assign all remaining not-yet 
;; destructured element to a sequence.
(let [[first second & rest] basket]
  (is (= "Apple" first))
  (is (= "Pear" second))
  (is (= ["Banana" "Grapes" "Lemon"] rest)))

;; We can use :as to get the original sequence.
(let [[first _ third :as fruits] basket]
  (is (= "Apple" first))
  (is (= "Banana" third))
  (is (= "APBGL" (apply str (map #(.charAt % 0) fruits)))))


;; Use destructure in function parameter to 
;; destructure the argument value when invoked.
(defn summary
  [[first second :as all]]
  (str first ", " second " and " (- (count all) 2) " more fruit names."))

(is (= "Apple, Pear and 3 more fruit names."
       (summary basket)))

Written with Clojure 1.10.1.

Clojure Goodness: Destructuring Maps

When we want to assign key values in a map to symbols we can use Clojure's powerful destructure options. With destructuring a map we can use dense syntax to assign keys to new symbols. For example we can use that in a let special form to assign symbols, but also for function parameters that are a map. When we use it for function parameters we can immediately assign keys to symbols we want to use in the function. Clojure provides a simple syntax to destructure a key value to a symbol using {symbol key} syntax. The value of :key will be assigned to symbol. We can provide default values if a key is not set in the map using :or followed by the symbol and default value. This is very useful if we know not all keys in a map will have values. Finally there is a shorthand syntax to assign keys to symbols with the same name as the key: :keys. We must provide a vector to :keys with the name of the keys, which will automatically assigned to symbols with the same name. To use this destructuring to its fullest the keys in the map must be keywords. We can use the keywordize-keys function in the clojure.walk namespace if we have a map with string keys and we want to transform them to keywords.

In the following example code we see several example of map destructuring:

(ns mrhaki.lang.destruct-map
  (:require [clojure.test :refer [is]]))

;; Sample map structure we want to destructure.
(def user {:first-name "Hubert"
           :last-name  "Klein Ikkink"
           :alias      "mrhaki"})

;; We can define a symbol username that will have the
;; the value of the :alias key of the user map.
(let [{username :alias} user]
  (is (= "mrhaki" username)))

;; When we use a non-existing key the symbol will
;; have a nil value, like the symbol city in the 
;; following example.
(let [{username :alias city :city} user]
  (is (nil? city))
  (is (= "mrhaki" username)))

;; We can use :or to define a value when a key
;; is not available in the map.
;; Here we define "Tilburg" as default value if
;; the :city key is missing from the map.
(let [{username :alias city :city :or {city "Tilburg"}} user]
  (is (= "Tilburg" city))
  (is (= "mrhaki" username)))

;; The symbol names must match in the definition
;; for the key value and the :or value.
(let [{username :alias lives-in :city :or {lives-in "Tilburg"}} user]
  (is (= "Tilburg" lives-in))
  (is (= "mrhaki" username)))

;; We can use :as to assign the original map
;; to a symbol, that we can use in the code.
(let [{username :alias :as person} user]
  (is (= "Hubert" (:first-name person)))
  (is (= "Klein Ikkink" (:last-name person)))
  (is (= "mrhaki" username)))

;; If the symbol name matches the key name we
;; can use :keys to define that so we have to type less.
(let [{:keys [alias first-name last-name]} user]
  (is (= "mrhaki" alias))
  (is (= "Hubert" first-name))
  (is (= "Klein Ikkink" last-name)))

;; Combination of destruturing options for a map.
(let [{:keys [first-name last-name city]
       :or   {city "Tilburg"}
       :as   person} user]
  (is (= "Hubert" first-name))
  (is (= "Klein Ikkink" last-name))
  (is (= "Tilburg" city))
  (is (= "mrhaki" (:alias person))))


;; Use destructuring in a function argument.
(defn who-am-i
  [{:keys [first-name last-name city]
    :or   {city "Tilburg"}
    :as   person}]
  (str first-name " " last-name ", aka " (:alias person) ", lives in " city))

(is (= "Hubert Klein Ikkink, aka mrhaki, lives in Tilburg"
       (who-am-i user)))
       
       
;; Another map with string keys. 
(def string-map {"alias" "mrhaki" "city" "Tilburg"})

(let [{username "alias" city "city"} string-map]
  (is (= "mrhaki" username))
  (is (= "Tilburg" city)))

;; We can use :strs instead of :keys for string keys.
(let [{:strs [alias city]} string-map]
  (is (= "mrhaki" alias))
  (is (= "Tilburg" city)))

;; Or convert string keys to keywords.
(let [{:keys [alias city]} (keywordize-keys string-map)]
  (is (= "mrhaki" alias))
  (is (= "Tilburg" city)))


;; For completeness we can destructure symbol keys.
(def sym-map {'alias "mrhaki" 'name "Hubert Klein Ikkink"})

(let [{username 'alias} sym-map]
  (is (= "mrhaki" username)))

;; We can use :str instead of :keys.
(let [{:syms [alias name]} sym-map]
  (is (= "mrhaki" alias))
  (is (= "Hubert Klein Ikkink" name)))

Written with Clojure 1.10.1.

February 8, 2021

Clojure Goodness: Remove Duplicates From A Collection With distinct

With the function distinct we can remove duplicate elements from a collection. The function returns a lazy sequence when we use a collection argument. Without arguments the function returns a transducer. When we want to remove duplicates and we don't need the lazy sequence result we could also turn a collection into a set with for example the set or into functions.

In the following example we use the distinct function on several collections.

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

;; In the following example we have the results
;; from several throws with a dice and we want
;; to remove all duplicates.
(is (= [1 5 6 2 3] (distinct [1 5 5 6 2 3 3 1])))

;; Only duplicates are removed.
(is (= ["Clojure" "Groovy" "Java"]
       (distinct ["Clojure" "Groovy" "Java" "Java" "Java" "Clojure"])))

;; String is also a collection we can invoke distinct function on.
(is (= [\a \b \c \d \e \f] (distinct "aabccdeff")))

;; For example a collection of mouse clicks where
;; we want to get rid of duplicate clicks at the same position.
(is (= [{:x 1 :y 1} {:x 1 :y 2} {:x 0 :y 0}]
       (distinct '({:x 1 :y 1} {:x 1 :y 2} {:x 1 :y 1} {:x 0 :y 0}))))
       
;; When we don't need the sequence result with ordening we can
;; also use a set to remove duplicates. 
;; We loose the order of the elements.
(is (= #{1 5 6 2 3}
       (set [1 5 6 5 2 3 1])
       (into #{} [1 5 6 5 2 3 1])))

Written with Clojure 1.10.1.

February 5, 2021

Clojure Goodness: Remove Consecutive Duplicate Elements From Collection

The Clojure core namespace contains many functions. One of the functions is the dedupe function. This function can remove consecutive duplicates from a collection and returns a lazy sequence where only one of the duplicates remain. It will not remove all duplicate elements from the collection, but only when the element is directly followed by a duplicate element. The function returns a transducer when no argument is given.

In the following code sample we use the dedupe function on several collections:

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

;; In the following example we have the results
;; from several throws with a dice and we want
;; remove duplicates that are thrown after another.
(is (= [1 5 6 2 3 1] (dedupe [1 5 5 6 2 3 3 1])))

;; Only consecutive duplicates are removed.
(is (= ["Clojure" "Groovy" "Java" "Clojure"]
       (dedupe ["Clojure" "Groovy" "Java" "Java" "Java" "Clojure"])))

;; String is also a collection.
(is (= [\a \b \c \d \e \f] (dedupe "aabccdeff")))

;; For example a collection of mouse clicks where
;; we want to get rid of consecutive clicks at the same position.
(is (= [{:x 1 :y 2} {:x 1 :y 1} {:x 0 :y 0}] 
       (dedupe '({:x 1 :y 2} {:x 1 :y 1} {:x 1 :y 1} {:x 0 :y 0}))))

Written with Clojure 1.10.1.

February 3, 2021

Clojure Goodness: Use .. For Invocation Java Method Chaining

Accessing Java from Clojure is easy. With the dot (.) special form we can invoke for example methods from a Java class or instance. If we want to invoke several methods together where the return value from one method is used to invoke the next method (method chaining) we can use the .. macro. The macro will expand into a nested expression with the . forms.

In the following example we see how to use the .. macro and how we can achieve the same result using nested . expressions and by using the thread first macro:

(ns mrhaki.java
  (:require [clojure.test :refer [is]])
  (:import (java.util Optional)))

(def value "Clojure")

;; We use Optional map method that accepts a java.util.function.Function,
;; so here we implement the Function interface with an implementation
;; to return the given String value in upper case.
(def fn-upper
  (reify java.util.function.Function
    (apply [this arg] (. arg toUpperCase))))

(is (= "CLOJURE"
       ;; Invoke Java method chaining using the special .. macro.
       ;; Java: Optional.ofNullable(value).map(s -> s.toUpperCase()).orElse("Default")
       (.. Optional (ofNullable value) (map fn-upper) (orElse "Default"))
       
       ;; Macro expands to the following equivalent using . form.
       (. (. (. Optional ofNullable value) (map fn-upper)) (orElse "Default"))
       
       ;; Using thread first macro with equivalent method invocations.
       (-> (Optional/ofNullable value)
           (. (map fn-upper))
           (. (orElse "Default")))))

(is (= "Default"
       (.. Optional (ofNullable nil) (map fn-upper) (orElse "Default"))))

Written with Clojure 1.10.1.

January 28, 2021

Clojure Goodness: Invoke Java Method With Varargs Parameter

Sometimes we want to invoke Java methods from our Clojure code. If the Java method accepts a variable arguments (varargs) parameter and we want to invoke the method from Clojure we must pass an array as argument. To create an array in Clojure we can use several functions. The to-array function will transform a collection to an Object[] type. For primitive type arrays we can use for example int-array to get a int[] array. The function into-array is the most flexible function. This function accepts a sequence argument and optionally the class type of the resulting array. Once we have the array we can use it as argument value for the varargs parameter of the Java method we want to invoke.

In the following example we use into-array, to-array and short-array to invoke a Java method with varargs parameter and see how we can build different array types:

(ns mrhaki.core.varargs
  (:require [clojure.test :refer [is]])
  (:import (java.text MessageFormat)))

;; We want to invoke the Java method MessageFormat/format that accepts
;; a format parameter followed by a varargs parameter.
(is (thrown-with-msg? ClassCastException 
                      #"java.lang.String incompatible with \[Ljava.lang.Object;" 
                      (MessageFormat/format "{0} is awesome." "Clojure")))
                      
;; Use into-array to transform sequence to Java array,
;; that can be used for methods that accept varargs parameter.
(is (= "Clojure is awesome."
       (MessageFormat/format "{0} is awesome."
                             (into-array ["Clojure"]))))

;; In the next example the type of the array is based on the first element
;; and becomes String[], but the sequence has also elements with other types.
;; We use the argment Object to have an Object[] array.
(is (= "Clojure contains 7 characters."
       (MessageFormat/format "{0} contains {1} characters."
                             (into-array Object ["Clojure" (count "Clojure")]))))

;; To get an Object[] array we could also use to-array function 
;; with a collection argument.
(is (= "Clojure contains 7 characters."
       (MessageFormat/format "{0} contains {1} characters."
                             (to-array ["Clojure" (count "Clojure")]))))

;; Type of first element sets array type.
(is (= "[Ljava.lang.String;"
       (.getName (class (into-array ["Clojure" "Groovy"])))))

;; Use explicit type or to-array function that always returns Object[] array.
(is (= "[Ljava.lang.Object;"
       (.getName (class (into-array Object ["Clojure" "Groovy"])))
       (.getName (class (to-array ["Clojure" "Groovy"])))))

;; Primitive types are transformed to array of boxed type: short, becomes Short.
(is (= "[Ljava.lang.Short;"
       (.getName (class (into-array (map short (range 5)))))))

;; We can get primitive type by using TYPE field of boxed type or
;; specific <primitive>-array function, like short-array.
(is (= "[S"
       (.getName (class (into-array Short/TYPE (map short (range 5)))))
       (.getName (class (short-array (range 5))))))

Written with Clojure 1.10.1.

November 2, 2020

Clojure Goodness: Getting Part Of A Vector With subvec

In Clojure we can get part of a vector collection using the subvec function. The function takes a vector as argument, a required begin index and optional end index. The returned value is a vector with part of the values of the original vector starting from the begin up to the end index. If we leave out the optional end index, the size of the vector is used as end index.

In the following example we use the subvec function with and without the end index:

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

;; Vector of some JVM languages.
(def languages ["Java" "Kotlin" "Clojure" "Groovy"])

;; Using only the start index argumnt we get all items
;; from the start index to the end.
(is (= ["Clojure" "Groovy"] (subvec languages 2)))

;; When we use the start and end index arguments
;; we get the items from start to the given end.
(is (= ["Clojure"] (subvec languages 2 3)))

Written with Clojure 1.10.1.

October 14, 2020

Clojure Goodness: Split Collection With Predicate

To split a collection in Clojure we can use the split-with and split-at functions. The split-with function takes a predicate as first argument and a colletion as second argument. The function will return a vector with two items. The first item is the result of the function take-while with the given predicate. The second item in the result vector is the resul of the drop-while function with the same predicate.

We use the split-at function with a number as first argument followed by a collection to split based on a given number of items. Instead of using a predicate we can define the number of items that we want as the first item in the result vector. The first item in the result vector is the result of invoking the take function. The resulting number of items of the collection will be the second item in the result vector and is achieved by invoking the drop function.

In the following example we use both functions with different arguments:

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

;; The split-with function has a predicate and returns the result 
;; of the functions take-while and drop-while in a result vector.
(let [less-than-5? (partial > 5)
      numbers      (range 11)]
  (is (= ['(0 1 2 3 4) '(5 6 7 8 9 10)]
         (split-with less-than-5? numbers))
         [(take-while less-than-5? numbers) (drop-while less-than-5? numbers)]))

;; In this example we take while the value is a String value and 
;; drop while starting from first value that is not a String.
(letfn [(string-value? [[k v]] (instance? String v))]
  (is (= ['([:language "Clojure"] [:alias "mrhaki"]) '([:age 47] [:country "NL"])]
         (split-with string-value? {:language "Clojure" :alias "mrhaki" :age 47 :country "NL"}))))


;; Instead of splitting on a predicate we can just give the number
;; of elements we want to split on with the split-at function.
(is (= ['(0 1 2 3) '(4 5 6 7)]
       (split-at 4 (range 8))
       [(take 4 (range 8)) (drop 4 (range 8))]))

(is (= ['([:language "Clojure"] [:alias "mrhaki"] [:age 47]) '([:country "NL"])]
       (split-at 3 {:language "Clojure" :alias "mrhaki" :age 47 :country "NL"})))

Clojure 1.10.1.

October 11, 2020

Clojure Goodness: Shuffle A Collection

In Clojure we can use the shuffle function with a collection argument to get a new collection where the items of the input collection are re-ordered randomly. The function delegates to the Java java.util.Collections#shuffle method.

In the following example code we use the shuffle method:

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

;; shuffle will return a new collection 
;; where the items are in a different order.
(shuffle (range 5)) ;; Possible collection [4 0 1 2 3]
(shuffle (range 5)) ;; Possible collection [1 3 4 2 0]

;; Define a deck of cards.
(def cards (for [suite  [\♥ \♠ \♣ \♦]
                 symbol (concat (range 2 11) [\J \Q \K \A])] 
             (str suite symbol)))

;; Some checks on our deck of cards.
(is (= 52 (count cards)))
(is (= (list "♥2" "♥3" "♥4" "♥5" "♥6" "♥7" "♥8" "♥9" "♥10" "♥J" "♥Q" "♥K" "♥A")
       (take 13 cards)))

;; Let's shuffle the deck. We get a new collection of cards ordered randomly.
(def shuffled-deck (shuffle cards))

;; Shuffled deck contains all items from the cards collection.
(is (true? (every? (set cards) shuffled-deck)))

;; We can take a number of cards.
(take 5 shuffled-deck) ;; Possible result: ("♦6" "♦10" "♥K" "♥4" "♥10")

;; We do a re-shuffle and get different cards now.
(take 5 (shuffle shuffled-deck)) ;; Possible result: ("♥10" "♥Q" "♦4" "♣8" "♠5")

Written with Clojure 1.10.1.

October 10, 2020

Clojure Goodness: Formatting With Java Format String

In Clojure we can format a string using Common Lisp format syntax or the Java format string syntax. In the post we will look at the how we can use the Java format string syntax. We must use the format function in the clojure.core namespace. The method delegates to the standard JDK String#format method. The first argument is a format string followed by one or more arguments that are used in the format string. We can look up the syntax of the format string in the Javadoc for the java.util.Formatter class.

In the following example code we use the format function with different format strings:

(ns mrhaki.string.format
  (:require [clojure.test :refer [is]])
  (:import (java.util Locale)))

;; Create new string with format string as template as first argument.
;; Following arguments are used to replace placeholders in the
;; format string.
;; Clojure will delegate to the java.lang.String#format method and
;; we can use all format string options that are defined for this method.
;; More details about the format string syntax can be found in
;; java.util.Formatter. In a REPL we can find the docs 
;; with (javadoc java.util.Formatter).
(is (= "https://www.mrhaki.com/"
       (format "https://%s/" "www.mrhaki.com")))

;; Format string with argument index to refer to one argument twice.
(is (= "clojure CLOJURE"
       (format "%1$s %1$S" "clojure")))

;; Format string to define fixed result lenght of 10 characers
;; with padding to get the given length.
(is (= "   Clojure"
       (format "%10s" "Clojure")))

;; Default Locale is used to determine how locale specific 
;; formats are applied. In the following example the default
;; decimal separator is . and group separator is , as specified
;; for the Canadian Locale.
(Locale/setDefault Locale/CANADA)
(is (= "Total: 42,000.00"
       (format "Total: %,.2f", 42000.0)))

(defn format-locale
  "Format a string using String/format with a Locale parameter"
  [locale fmt & args]
  (String/format locale fmt (to-array args)))

;; We can use a different Locale to apply different specific 
;; locale formats. In the next example we use the Dutch Locale
;; and the decimal seperator is , and the group separator is ..
(is (= "Totaal: 42.000,00"
       (format-locale (Locale. "nl") "Totaal: %,.2f" 42000.0)))

Written with Clojure 1.10.1.

October 6, 2020

Clojure Goodness: Finding The Maximum Or Minimum Value

To find the maximum or minimum value for numeric values we can use the max and min function. The functions accept one or more numeric arguments and the value that is maximum or minimum is returned. If the numbers are already in a sequence we can use apply max or apply min. If the values are not numbers we can use the max-key or min-key functions. These functions take as first argument a function that returns a number. So we can get the value that has the maximum or minimum return value for the function we pass as first argument.

In the next exmaple code we use the max, min, max-key and min-key functions:

(ns mrhaki.core.min-max
  (:require [clojure.test :refer [is]]))

;; We can use max to find the maximum number in the given arguments.
(is (= 20 (max 10 2 3 1 20)))

;; If we have a collection we can use apply max to find the maximum number.
(is (= 20 (apply max [10 2 3 1 20])))


;; We can use min to find the minimum number in the given arguments.
(is (= 1 (min 10 2 3 1 20)))

;; And also use apply min when we have collection with numbers.
(is (= 1 (apply min [10 2 3 1 20])))


;; When the arguments are not numbers we can provide a function to get
;; back numbers and use that function with max-key to find the maximum.
(is (= "Clojure" (max-key count "Java" "Groovy" "Clojure")))
(is (= "Clojure" (apply max-key count ["Java" "Groovy" "Clojure"])))


;; And to find the minimum for non-numbered arguments we can use min-key
;; with a function to get back numbers.
(is (= "Java" (min-key count "Java" "Groovy" "Clojure")))
(is (= "Java" (apply min-key count ["Java" "Groovy" "Clojure"])))

Written with Clojure 1.10.1.