Table of Contents
- About Me
- Introduction
- Core
-
Functions
- Create New Function Complementing Other Function
- Repeating A Value Or Function Invocation
- Composing Functions With comp
- Set Default Value For nil Function Argument With fnil
- Getting Results From Multiple Functions With Juxt Function
- Pure Function Sample Buying Coffee From FP Programming In Scala Written In Clojure
- Strings
-
Collections
- Merge Maps With Function To Set Value Duplicate Keys
- Split Collection With Predicate
- Concatenation Of Map Function Results With mapcat
- Transforming Collection Items With Index
- Repeat Items In A Collection As Lazy Sequence With cycle
- Interleave Keys And Values Into A Map With zipmap
- Using The range Function
- Combine First And Next Functions Multiple Times
- Get Random Item From A Sequence
- Query Set Of Maps With index Function
- Creating Union Of Sets
- Find Differences In Sets
- Getting But The Last Element(s) From A Collection
- Remove Duplicates From A Collection With distinct
- Remove Consecutive Duplicate Elements From Collection
- Getting Part Of A Vector With subvec
- Shuffle A Collection
- Finding The Maximum Or Minimum Value
- Taking Or Dropping Elements From A Collection Based On Predicate
- Partition Collection Into Sequences
- Counting Frequency Of Items In A Collection
- Reapply Function With Iterate To Create Infinite Sequence
- Checking Predicate For Every Or Any Element In A Collection
- Keep Non-Nil Function Results From Collection
- Flatten Collections
- Getting Intersections Between Sets
- Files
- Java Interoperability
About Me
I am born in 1973 and live in Tilburg, the Netherlands, with my three gorgeous children. I am also known as mrhaki, which is simply the initials of his name prepended by mr. The following Clojure snippet shows how the alias comes together:
(assert
(= "mrhaki"
(reduce
(fn [nickname name] (str nickname (clojure.string/lower-case (first name))))
"mr"
["Hubert" "Alexander" "Klein" "Ikkink"])))))
(How cool is Clojure that we can express this in a simple code sample ;-) )
I studied Information Systems and Management at the Tilburg University. After finishing my studies I started to work at a company which specialized in knowledge-based software. There I started writing my first Java software (yes, an applet!) in 1996. Over the years my focus switched from applets, to servlets, to Java Enterprise Edition applications, to Spring-based software.
In 2008 I wanted to have fun again when writing software. The larger projects I was working on were more about writing configuration XML files, tuning performance and less about real development. So I started to look around and noticed Groovy as a good language to learn about. I could still use existing Java code, libraries, and my Groovy classes in Java. The learning curve isn’t steep and to support my learning phase I wrote down interesting Groovy facts in my blog with the title Groovy Goodness. I post small articles with a lot of code samples to understand how to use Groovy. Since November 2011 I am also a DZone Most Valuable Blogger (MVB); DZone also posts my blog items on their site. After writing about Groovy I noticed that writing about a subject while learning the subject is very useful. So it became an habit to start writing small blog posts when I learned something new.
I have spoken at the Gr8Conf Europe and US editions about Groovy, Gradle, Grails and Asciidoctor topics. Other conferences where I talked are Greach in Madrid, Spain, JavaLand in Germany and JFall in The Netherlands.
I work for a company called JDriven in the Netherlands. JDriven focuses on technologies that simplify and improve development of enterprise applications. Employees of JDriven have years of experience with Java and related technologies and are all eager to learn about new technologies.
Introduction
When I started to learn about Clojure I wrote done little code snippets with features of Clojure I found interesting. To access my notes from different locations I wrote the snippets with a short explanation in a blog: Messages from mrhaki. I labeled the post as Clojure Goodness, because I thought Clojure is really nice and elegant, and that is how the Clojure Goodness series began.
A while ago I bundled all my blog Groovy Goodness blog posts in a book published at Leanpub. Leanpub is very easy to use and I could use Markdown to write the content, which I really liked as a developer. So it felt natural to also bundle the Clojure Goodness blog posts in a book and publish at Leanpub.
In this book the blog posts are bundled and categorized into sections. Within each section blog posts that cover similar features are grouped. The book is intended to browse through the subjects. You should be able to just open the book at a random page and learn more about Clojure Maybe pick it up once in a while and learn a bit more about known and lesser known features of Clojure
I hope you will enjoy reading the book and it will help you with learning about Clojure, so you can apply all the goodness in your projects.
Core
There Is Truth In Many Things
In Clojure there are only two things false in a boolean or conditional context: false
and nil
. All other values are threated as true. A value that is false in a boolean context is also called falsey, and a value that is true is called truthy.
In the following example we use false
and nil
as falsey and several other values as truthy values:
(ns mrhaki.core.truthy-falsey
(:require [clojure.test :refer [is]]))
;; Only nil and false are falsey.
(is (= false (boolean false)))
(is (= false (boolean nil)))
(is (= false (boolean ({:cool "Clojure"} :language))))
(is (= :falsey (if nil :truthy :falsey)))
;; Other values are truthy.
(is (= true (boolean true)))
(is (= true (boolean "mrhaki")))
(is (= true (boolean 0)))
(is (= true (boolean 1)))
(is (= true (boolean '())))
(is (= true (boolean [1 2 3])))
(is (= true (boolean {})))
(is (= true (boolean ({:rocks "Clojure"} :rocks))))
(is (= :truthy (if "Clojure" :truthy :falsey)))
(is (= :truthy (if "" :truthy :falsey)))
(is (= :empty-but-truthy (if [] :empty-but-truthy :empty-and-falsey)))
Written with Clojure 1.10.1.
Original post written on October 5, 2020
Get Clojure Version
To get the current Clojure version we must use the clojure-version
function. The function simply returns the Clojure version we are using from our code.
In the following example we simply check the result of clojure-version
and also define a function to get the Javaa version:
(ns mrhaki.core.version
(:require [clojure.test :refer [is]]))
;; Function clojure-version returns Clojure version.
(is (= "1.10.1" (clojure-version)))
(defn java-version
"Returns Java version as printable string."
[]
(System/getProperty "java.version"))
(is (= "14" (java-version)))
Written with Clojure 1.10.1.
Original post written on April 6, 2020
Keyword As Function
In Clojure functions are everywhere. In a previous post we learned that sets can be functions, but Clojure also makes keywords functions. A keyword is a symbol starting with a colon (:
) and is mostly used in map entries as key symbol. The keyword as function accepts a map as single argument and returns the value for the key that equals the keyword in the map or nil
if the keyword cannot be found.
In the following code we use keywords as function in several examples:
(ns mrhaki.core.keyword-function
(:require [clojure.test :refer [is]]))
;; Sample map to use in examples.
(def user {:name "Hubert" :alias "mrhaki" :living {:country "Netherlands"}})
;; Keyword is a function with a map argument and
;; returns value for keyword in the map.
(is (= "mrhaki"
(:alias user)
;; We get the same result with get.
(get user :alias)))
(is (= {:country "Netherlands"} (:living user)))
(is (= "Netherlands"
(:country (:living user))
(-> user :living :country)
;; We can use get-in to get values from nested maps.
(get-in user [:living :country])))
;; When keyword is not in the map we get a nil result.
(is (nil? (:city user)))
(is (= "not-found" (or (:city user) "not-found")))
;; Works also for namespaced keywords.
(is (= "mrhaki" (:user/alias {:name/full-name "Hubert" :user/alias "mrhaki"})))
;; Using keyword as function with juxt.
(is (= ["mrhaki" "Hubert"]
((juxt :alias :name) user)))
Written with Clojure 1.10.1.
Original post written on June 25, 2020
Using Sets As Functions
One of the nice things in Clojure is that some data structures are also functions. For me this felt rather strange when learning Clojure (coming from Java), but it can be very powerful. A set in Clojure is also a function. The set as function accept a single argument and it return nil
when the argument is not part of the set, otherwise the argument value is returned. This behaviour also makes a set as function a nice predicate to be used for example in collection functions.
In the following example code we use different sets as function:
(ns mrhaki.set.function
(:require [clojure.test :refer [is]]))
;; Sample set with some JVM languages.
(def languages #{"Clojure" "Groovy" "Kotlin"})
;; We can use the set languages as function
;; with one argument to check if the argument
;; is part of the set.
(is (= "Clojure" (languages "Clojure")))
;; If the argument is not part of the set
;; we get back nil.
(is (= nil (languages "Java")))
;; As nil is logical false in Clojure
;; a set makes a nice predicate.
(is (= ["Clojure"] (filter #{"Clojure" "Java"} languages)))
(is (= ["Kotlin" "Groovy"] (remove #{"Java" "Clojure"} languages)))
;; Sample vector with numbers.
(def numbers [0 2 1 4 2 3 1 0])
;; Use set as predicate.
(is (= [2 1 2 1] (filter #{1 2} numbers)))
;; As set #{1 2} is a function we can use it as argument
;; for other functions to create a new function.
(is (= [0 4 3 0] (filter (complement #{1 2}) numbers)))
Written with Clojure 1.10.1.
Original post written on June 19, 2020
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.
Original post written on February 9, 2021
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.
Original post written on February 9, 2021
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
Strings
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.
Original post written on October 10, 2020
Trimming Strings
In the clojure.string
namespace we can find several useful function for working with strings. If we want to trim a string we can choose for the trim
, trial
, trimr
and trim-newline
functions. To trim all characters before a string we must use the triml
function. To remove all space characters after a string we use trimr
. To remove space characters both before and after a string we can use the trim
function. Finally if we only want to remove the newline and/or return characters we use the trim-newline
function.
In the following example we use the different trim functions on strings:
(ns mrhaki.sample
(:require [clojure.test :refer [is]]
[clojure.string :refer [trim triml trimr trim-newline]]))
;; The trim function removes spaces before and after the string.
(is (= "mrhaki" (trim " mrhaki ")))
;; Tabs are also trimmed.
(is (= "mrhaki" (trim "\t mrhaki ")))
;; Return and/or newline characters are also trimmed.
(is (= "mrhaki" (trim "\tmrhaki \r\n")))
;; Character literals that should be trimmed are trimmed.
(is (= "mrhaki" (trim (str \tab \space " mrhaki " \newline))))
;; The triml function removes spaces before the string (trim left).
(is (= "mrhaki " (triml " mrhaki ")))
(is (= "mrhaki " (triml "\t mrhaki ")))
(is (= "mrhaki " (triml "\nmrhaki ")))
(is (= "mrhaki " (triml (str \return \newline " mrhaki "))))
;; The trimr function removes spaces after the string (trim right).
(is (= " mrhaki" (trimr " mrhaki ")))
(is (= " mrhaki" (trimr " mrhaki\t")))
(is (= " mrhaki" (trimr (str " mrhaki " \newline))))
;; The trim-newline function removes only newline from string.
(is (= "mrhaki " (trim-newline (str "mrhaki " \newline))))
(is (= "mrhaki " (trim-newline (str "mrhaki " \return \newline))))
(is (= "mrhaki " (trim-newline "mrhaki \r\n")))
(is (= "mrhaki " (trim-newline "mrhaki ")))
Written with Clojure 1.10.1.
Original post written on January 3, 2020
Check Substring Is Part Of String
Sometimes we want to see if a string is part of another string. Or if the string value starts or ends with a certain string. In Clojure we can use the includes?
function from the clojure.string
namespace to check if a string is part of another string value. To see if a string starts with a certain value we can use the starts-with?
function from the clojure.string
namespace. And to check if a string ends with a given value we use ends-with?
from the same namespace.
In the next example code we use these functions and also add a matches?
function to check if a string matches with a regular expression defined in a string:
(ns mrhaki.string.includes
(:require [clojure.string :as str]
[clojure.test :refer [is]]))
;; String to check.
(def s "Clojure is cool!")
;; Check if given value is part of the string.
(is (true? (str/includes? s "cool")))
(is (false? (str/includes? s "boring")))
;; Check string starts with given value.
(is (true? (str/starts-with? s "Clojure")))
(is (false? (str/starts-with? s "Groovy")))
;; Check string ends with given value.
(is (true? (str/ends-with? s "cool!")))
;; Helper function to see if string with regular expression
;; matches a given string value using java.lang.String#matches.
(defn matches?
"Return true when string `re` with regular expression
matches for value `s`, false otherwise."
[^CharSequence s ^CharSequence re]
(. s matches re))
(is (true? (matches? s ".*is.*")))
(is (false? (matches? s "cool")))
Written with Clojure 1.10.1.
Original post written on April 22, 2020
Splitting Strings
In Clojure we can use the clojure.string/split
function to split a string, based on a regular expression, into a vector with string values. Optionally we can also specify a limit on the maximum number of returned string values we want. If we want to split a string based on the newline characters we can use the function clojure.string/split-lines
that returns a vector where each element is a line from the original multi-line string.
The following example shows several usages of the split
and split-lines
functions:
(ns mrhaki.string.split
(:require [clojure.string :as str]
[clojure.test :refer [is are]]))
;; Sample string to split.
(def issue "CLJ-90210: Subject")
;; Split on - and : to get vector with string values.
(is (= ["CLJ" "90210" "Subject"] (str/split issue #"-|: ")))
;; The split function accepts a third argument that is
;; a limit on the number of splits that are returned.
(is (= ["CLJ" "90210" "Subject"]
(str/split issue #"-|: " 0)
(str/split issue #"-|: " 3)))
(is (= [issue] (str/split issue #"-|: " 1)))
(is (= ["CLJ" "90210: Subject"] (str/split issue #"-|: " 2)))
;; Multiline sample string to split per line and
;; the split each line.
(def itinerary "loc:LVG time:15h-16h activity:Binge-watching
loc:DNR time:18h-19h activity:Eating
loc:MBR time:23h-7h activity:Sleeping")
;; Using split-line function we get a vector
;; where each line is an element.
;; Then for each line we split on : and \s+ and
;; convert it to a map.
;; E.g. first line is
;; {"loc" "LVG" "time" "15h-16h" "activity" "Binge-watching"}
(def agenda (map #(apply hash-map (str/split % #":|\s+"))
(str/split-lines itinerary)))
(is (= "LVG" ((first agenda) "loc")))
(is (= "15h-16h" ((first agenda) "time")))
(is (= "Binge-watching" ((first agenda) "activity")))
(is (= "DNR" ((nth agenda 1) "loc")))
(is (= "18h-19h" ((nth agenda 1) "time")))
(is (= "Eating" ((nth agenda 1) "activity")))
(are [value m key] (= value ((last m) key))
"MBR" agenda "loc"
"23h-7h" agenda "time"
"Sleeping" agenda "activity")
Written with Clojure 1.10.1.
Original post written on April 1, 2020
Joining Elements in a Collection
We can use the join
function from the clojure.string
namespace to join elements from a collection into a string. We can optionally specify a separator that is used to separate each element in the string output. The separator is not used after the last element of the collection. If we don't specify a separator the elements are concatenated without separation. The string representation for each element in the collection is used in the joined end result.
In the following example code we see different usages of the join
function:
(ns mrhaki.sample
(:import [java.util Currency Locale])
(:require [clojure.string :refer [join]]
[clojure.test :refer [is]]))
;; Join without explicit separator simply concats values in collection.
(is (= "abc" (join ["a" "b" "c"])))
;; Join with separator uses separator between elements from collection
;; and omits the separator after the last element.
(is (= "a, b, c" (join ", " ["a" "b" "c"])))
;; Join works on multiple collection types,
;; because each collection is transformed to a seq.
(is (= "a::b::c" (join "::" #{"a" "b" "c"})))
;; Collection with non-strings is also returned as string.
;; The string representation of each element is used.
(is (= "0 1 2 3 4 5 6 7 8 9 10" (join " " (range 11))))
(is (= "https://www.mrhaki.com:443/,EUR" (join \, [(java.net.URL. "https" "www.mrhaki.com" 443 "/")
(Currency/getInstance (Locale. "nl" "NL"))])))
;; Nil values are ignored in the join results,
;; but separator is still used for nil element.
(is (= "Clojure--is cool--!" (join "-" ["Clojure" nil "is cool" nil "!"])))
;; Function query-params to transform a map structure with
;; keyword keys to URL request parameters.
(defn query-params
"Return key/value pairs as HTTP request parameters separated by &.
Each request parameter name and value is separated by =.
E.g. {:q \"Clojure\" :max 10 :start 0 :format \"xml\"} is transformed
to q=Clojure&max=10&start=0&format=xml."
[params]
(let [query-param (fn [[param-name param-value]] (join "=" [(name param-name) param-value]))]
(join "&" (map query-param params))))
(is (= "q=Clojure&max=10&start=0&format=xml" (query-params {:q "Clojure" :max 10 :start 0 :format "xml\
"})))
Written with Clojure 1.10.1
Original post written on January 6, 2020
Replacing Characters In A String With escape Function
The clojure.string
namespace contains a lot of useful functions to work with string values. The escape
function can be used to replace characters in a string with another character. The function accepts as first argument the string value and the second argument is a map. The map has characters as key that need to be replaced followed by the value it is replaced with. For example the map {\a 1 \b 2}
replaces the character a
with 1
and the character b
with 2
.
In the following example code we use the escape
function in several cases:
(ns mrhaki.string.escape-string
(:require [clojure.string :as str]
[clojure.test :refer [is]]))
(is (= "I 10v3 C10jur3"
(str/escape "I love Clojure" {\o 0 \e 3 \l 1})))
(is (= "mrHAKI"
(str/escape "mrhaki" {\h "H" \a "A" \k "K" \i "I" \x "X"})))
(def html-escaping {(char 60) "<" (char 62) ">" (char 38) "&"})
(is (= "<h1>Clojure & Groovy rocks!</h1>"
(str/escape "<h1>Clojure & Groovy rocks!</h1>" html-escaping)))
(is (= "Special chars: \\t \\n"
(str/escape "Special chars: \t \n" char-escape-string)))
Written with Clojure 1.10.1.
Original post written on July 8, 2020
Replacing Matching Values In String
We can search for a value in a string and replace it with another value using the clojure.string/replace
function. The first parameter is the original string value that we want to replace parts of. The second parameter can be a string value or regular expression. The last parameter is the replacement value that can be a string value or a function that returns a string value. The function itself gets either a string argument if the match has no nested groups (when match is a regular expression) or a vector with a complete match followed by the nested groups when the match has nested groups.
In the following example we several invocation of the clojure.string/replace
function with different arguments:
(ns mrhaki.string.replace
(:require [clojure.string :as str]
[clojure.test :refer [is]]))
;; Example string value to do replacements on.
(def s "Programming with Clojure is fun!")
;; Match argument can be a string value,
;; that gives same result as java.lang.String#replace method.
(is (= "Programming with Clojure is awesome!"
(str/replace s "fun" "awesome")
(.replace s "fun" "awesome")))
;; Match argument can also be regular expression pattern.
(is (= "Programming_with_Clojure_is_fun!"
(str/replace s #"\s+" "_")))
;; When the regular expression pattern has groups
;; we can refer to them using $ followed by matched
;; group number, eg. $1 for the first group.
(is (= "Execution 1 took 200ms"
(str/replace "run1=200ms" #"run(\d+)=(\d+ms)" "Execution $1 took $2")))
;; Replace argument can be a function.
;; Argument of the function is string of entire match
;; if there are no nested groups.
(is (= "[NOTE] [CAUTION]"
(str/replace "[note] [caution]" #"\[\w+\]" #(.toUpperCase %))))
;; Otherwise if there are nested groups a vector is
;; used as argument for the replacment function
;; where the first argument is the
;; entire match followed by the nested groups.
(is (= "ABC def"
(str/replace "abc DEF"
#"(\w+)(\s+)(\w+)"
#(str (.toUpperCase (% 1)) (% 2) (.toLowerCase (% 3))))))
;; By destructuring the vector argument
;; we can refer to the groups using a name.
(defn replacement
[[_ execution time]]
(let [seconds (/ (bigdec time) 1000)]
(str "Execution " execution " took " seconds " seconds")))
(is (= "Execution 1 took 0.2 seconds"
(str/replace "run1=200ms" #"run(\d+)=(\d+)ms" replacement)))
Written with Clojure 1.10.1.
Original post written on March 29, 2020
Collections
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.
Original post written on February 23, 2021
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.
Original post written on October 14, 2020
Concatenation Of Map Function Results With mapcat
When we use a function as argument for the map
function that returns a collection we would get nested collections. If we want to turn the result into a single collection we can concatenate the elements from the collections by applying the concat
function, but we can do this directly with the function mapcat
. The function mapcat
takes as first argument a function (that returns a collection) and one or more collections as next arguments.
In the following examples we see several uses of mapcat
:
(ns mrhaki.core.mapcat
(:require [clojure.test :refer [is]]))
;; The function argument for mapcat returns a collection
;; with the original element of the collection
;; and the value added by 10.
(is (= [1 11 2 12 3 13]
(mapcat (fn [n] [n (+ 10 n)]) [1 2 3])))
(is (= [1 1 2 2 3 3]
(mapcat (partial repeat 2) [1 2 3])
;; Using apply concat with map returns the same result.
(apply concat (map (partial repeat 2) [1 2 3]))))
;; Combined with juxt
(is (= ["mrhaki" 6 "blog" 4]
(mapcat (juxt identity count) ["mrhaki" "blog"])))
;; Our first example rewritten with juxt.
(is (= [1 11 2 12 3 13]
(mapcat (juxt identity (partial + 10)) [1 2 3])))
;; We can use multiple collections,
;; the function then accepts multiple arguments.
(is (= [1 100 100 2 200 400 3 300 900]
(mapcat (fn [a b] [a b (* a b)]) [1 2 3] [100 200 300])))
Written with Clojure 1.10.1.
Original post written on July 5, 2020
Transforming Collection Items With Index
If we want to transform items in a collection we can use the map
function. If we also want to use the index of the element in the collection in the transformation we must use the map-indexed
function. We must provide a function with 2 arguments, where the first argument is the index of the element in the collection and the second argument is the element in the collection.
In the following examples we use the map-indexed
function:
(ns mrhaki.core.map-indexed
(:require [clojure.test :refer [is]]))
;; map-indexed applies a function to each element
;; in a collection where the function gets the
;; index of the item in the collection and the item itself.
(is (= [[0 3] [1 20] [2 10] [3 2] [4 1]]
(map-indexed (fn [index number] [index number]) [3 20 10 2 1])))
(defn indices
"Return lazy sequence of indices of elements in a collection."
[coll]
(map-indexed (fn [index _] index) coll))
(is (= [0 1 2 3 4] (indices [3 20 10 2 1])))
(defn char-range
"Function to return a range of characters from `start` to `end` (including)."
[start end]
(map char (range (int start) (inc (int end)))))
(def a-z (char-range \a \z)) ;; characters from a to z.
;; map-indexed returns a lazy sequence.
(is (= [[\a 0] [\b 1] [\c 2]]
(take 3 (map-indexed (fn [index ch] [ch index]) a-z))))
;; Create map with letter keys and position in alphabet as values.
(def letters-positions (into {} (map-indexed (fn [index ch] [ch (inc index)]) a-z)))
(is (= [[\a 1] [\b 2] [\c 3]]
(take 3 letters-positions)))
;; Find position of each letter of word "clojure".
(is (= [3 12 15 10 21 18 5]
(reduce (fn [result value] (conj result (get letters-positions value)))
[]
"clojure")))
Written with Clojure 1.10.1.
Original post written on June 8, 2020
Repeat Items In A Collection As Lazy Sequence With cycle
The Clojure function cycle
take a collections as argument and creates a lazy sequence by repeating the items in the collection. So if we pass a collection with the characters \a
, \b
and \c
we get a lazy sequence of (\a \b \c \a \b \c ...)
.
(ns mrhaki.core.cycle
(:require [clojure.test :refer [is]]))
;; The items in the collection are repeated
;; and return type is a lazy sequence.
(is (= [0 1 0 1 0 1]
(take 6 (cycle [0 1]))))
(is (seq? (cycle [0 1])))
(is (= [\C \l \o \j \u \r \e \C \l \o \j \u \r \e]
(take 14 (cycle "Clojure"))))
;; Useful for functions that want equally sized
;; collection arguments.
(is (= {:a 0 :b 1 :c 0 :d 1}
(zipmap [:a :b :c :d] (cycle [0 1]))))
Written with Clojure 1.10.1.
Original post written on June 2, 2020
Interleave Keys And Values Into A Map With zipmap
The Clojure function zipmap
create a map by interleaving a collection of keys with a collection of values. The first element of the keys collection is the map entry keyword and the first element of the values collection is than the map entry value, and so on for the other elements in the keys and values collections.
In the following example code we use zipmap
using different examples of keys and values collections:
(ns mrhaki.core.zipmap
(:require [clojure.test :refer [is]]))
;; zipmap creates a map with keys from the
;; first collection and values from the second.
(is (= {:name "Hubert" :alias "mrhaki"}
(zipmap [:name :alias] ["Hubert" "mrhaki"])))
;; If the size of the values collection is smaller
;; than the size of the keys collection, only
;; keys that map to a value end in up
;; in the resulting map.
(is (= {:name "Hubert"}
(zipmap [:name :alias] ["Hubert"])))
;; If the size of the keys collection is smaller
;; than the size of the value collection, then the
;; returned map only contains keys from the keys
;; collection and some values are ignored.
(is (= {:name "Hubert"}
(zipmap [:name] ["Hubert" "mrhaki"])))
;; Using a lazy sequence created by the repeat
;; function we can set a default value for all keys.
(is (= {:name "" :alias "" :city ""}
(zipmap [:name :alias :city] (repeat ""))))
;; If we have keys with the same name the last
;; mapping ends up in the resulting map.
(is (= {:name "mrhaki"}
(zipmap [:name :name] ["Hubert" "mrhaki"])))
;; Keys for the resulting map don't have to be keywords,
;; but can be any type.
(is (= {"name" "Hubert" "alias" "mrhaki"}
(zipmap ["name" "alias"] ["Hubert" "mrhaki"])))
Written with Clojure 1.10.1.
Original post written on May 29, 2020
Using The range Function
In Clojure we can use the range
function to create a lazy sequence of numbers. We can optionally specify a start value, end value and define the steps between the numbers. If we use the end value argument that value is exclusive for the returned values in the lazy sequence.
In the following example we invoke the range
function with different arguments:
(ns mrhaki.core.range
(:require [clojure.test :refer [is]]))
;; range function without arguments returns
;; an infinite lazy sequence of numbers.
(is (= '(0 1 2 3 4) (take 5 (range))))
;; We can specifyt the start value for
;; a lazy infinite sequence of numbers.
(is (= '(0 1 2 3 4) (range 5)))
;; With the second argument we set the
;; end value for our lazy sequence of numbers.
;; The end value is exclusive for the range.
(is (= '(5 6 7 8 9) (range 5 10)))
;; The third argument defines the step value
;; between numbers, which by default is 1.
(is (= '(0 2 4 6 8) (range 0 10 2)))
;; We can also have a lazy sequence counting
;; numbers back.
(is (= '(5 4 3 2 1) (range 5 0 -1)))
(is (= '(100 97 94 91 88) (take 5 (range 100 0 -3))))
Written with Clojure 1.10.1.
Original post written on May 13, 2020
Combine First And Next Functions Multiple Times
The first
function in Clojure returns the first item of a collection. The next
function returns a new sequence with all elements after the first element from a collection. Clojure adds some utility methods to combine first
and next
with different combinations. We can use the function ffirst
which is will return the first element of the first element of a collection and the nfirst
function to get the next elements from the first element of a collection. We can use the function fnext
to get the first element of the next elements of a collection and the function nnext
to get the next elements of the next elements of a collection.
In the following example we use the ffirst
, nfirst
, fnext
and nnext
:
(ns mrhaki.seq
(:require [clojure.test :refer [is]]))
(def langs [{:language "Clojure" :site "https://clojure.org" :dynamic true}
{:language "Groovy" :site "https://www.groovy-lang.org" :dynamic true}
{:language "Java" :site "https://www.java.com" :dynamic false}
{:language "Kotlin" :site "https://kotlinlang.org" :dynamic false}])
;; Find first map entry of first map in languages.
(is (= [:language "Clojure"]
(ffirst langs)
(first (first langs))))
;; Find next map entries for first map in languages.
(is (= (list [:site "https://clojure.org"] [:dynamic true])
(nfirst langs)
(next (first langs))))
;; Find first map of next maps in languages.
(is (= {:language "Groovy" :site "https://www.groovy-lang.org" :dynamic true}
(fnext langs)
(first (next langs))))
;; Find next maps of next maps in languages.
(is (= (list {:language "Java" :site "https://www.java.com" :dynamic false}
{:language "Kotlin" :site "https://kotlinlang.org" :dynamic false})
(nnext langs)
(next (next langs))))
Written with Clojure 1.10.1.
Original post written on April 13, 2020
Get Random Item From A Sequence
In Clojure we can use the rand-nth
function to get a single random element from a sequence. To get multiple items based on random probability for each item we use the function random-sample
. We must set the probability that determines for each item if it is in the result or not.
In the following example code we use rand-nth
function:
(ns mrhaki.seq.random
(:require [clojure.test :refer [is]]))
;; We use the function rand-nth to get a
;; random element from a sequence collection.
(is (contains? #{"Clojure" "Java" "Groovy"}
(rand-nth ["Groovy", "Clojure", "Java"])))
;; We can use the rand-nth function with a map
;; if we first turn it into a sequence.
(is (contains? #{[:a 1] [:b 2]} (rand-nth (seq {:a 1 :b 2}))))
This next example shows how we can use the random-sample
function:
(ns mrhaki.seq.random
(:require [clojure.test :refer [is]]))
;; Using random-sample each item is in the
;; result based on the random probability of the
;; probability argument.
;; When probability is 1 all items are returned.
(is (= ["Clojure" "Groovy" "Java"]
(random-sample 1.0 ["Clojure" "Groovy" "Java"])))
;; When proability is 0 no item is in the result.
(is (empty? (random-sample 0 ["Clojure" "Groovy" "Java"])))
;; Any other value between 0 and 1 will return different
;; results for each invocation of the random-sample function.
(def samples (random-sample 0.4 ["Clojure" "Groovy"]))
(is (or (empty? samples)
(= ["Clojure" "Groovy"] samples)
(= ["Clojure"] samples)
(= ["Groovy"] samples)))
Written with Clojure 1.10.1
Original post written on March 30, 2020
Query Set Of Maps With index Function
The namespace clojure.set
has useful functions when we work with sets. One of the functions is the index
function. The index
function can be used for a set with map elements to create a new map based on distinct values for one or more keys. The first argument of the function is the set we transform and the second argument is a vector of one or more keys we want to index on. The keys in the new map are maps themselves. The value for each key is a set of maps that have the given keyword/value combination. The new map can be easily queried with the get
function to get the values for a key.
In the next example code we see how we can use the clojure.set/index
function to first transform a set with map elements to the new map and how to work with the resulting map:
(ns mrhaki.set.index
(:require [clojure.test :refer [is]]
[clojure.set :refer [index]]))
(def languages #{{:platform :jvm :name "Clojure"}
{:platform :jvm :name "Groovy"}
{:platform :native :name "Ruby"}
{:platform :jvm :name "JRuby"}
{:platform :native :name "Rust"}})
;; index function returns a map with a key for
;; each unique key/value combination for the keys
;; passed as second argument.
;; The value of each key is a set of the
;; map that comply with the key/value combination.
(is (= {{:platform :jvm} #{{:platform :jvm :name "Clojure"}
{:platform :jvm :name "Groovy"}
{:platform :jvm :name "JRuby"}}
{:platform :native} #{{:platform :native :name "Ruby"}
{:platform :native :name "Rust"}}}
(index languages [:platform])))
;; We can use all collection functions on the map result
;; of the index function.
(is (= ["Clojure" "Groovy" "JRuby"]
(map :name (get (index languages [:platform]) {:platform :jvm}))))
;; Set with sample data describing a shape
;; at a x and y location.
(def data #{{:shape :rectangle :x 100 :y 100}
{:shape :circle :x 100 :y 100}
{:shape :circle :x 100 :y 0}
{:shape :circle :x 0 :y 100}})
;; We can use multiple keys as second argument of the
;; index function if we want to index on values of
;; more thane one key.
(is (= {{:x 0 :y 100} #{{:shape :circle :x 0 :y 100}}
{:x 100 :y 0} #{{:shape :circle :x 100 :y 0}}
{:x 100 :y 100} #{{:shape :circle :x 100 :y 100}
{:shape :rectangle :x 100 :y 100}}}
(index data [:x :y])))
(is (= #{{:shape :circle :x 100 :y 100}
{:shape :rectangle :x 100 :y 100}}
(get (index data [:x :y]) {:x 100 :y 100})))
Written with Clojure 1.10.1.
Original post written on July 23, 2020
Creating Union Of Sets
When we are working with sets in Clojure we can use some useful functions from the clojure.set
namespace. In a previous post we learned how we can get the difference of several sets. To get the union of several input sets we use the union
function of the clojure.set
namespace. The function returns a new set that is the union of unique elements from the input sets. A nil
value is ignored by the union
function.
In the following example code we use union
:
(ns mrhaki.set.union
(:require [clojure.set :as set]
[clojure.test :refer [is]]))
;; union return a set with elements that contains the unique
;; elements of the input sets.
(is (= #{"Java" "Clojure" "Groovy" "Kotlin"}
(set/union #{"Java" "Clojure" "Groovy"} #{"Kotlin" "Groovy" "Clojure"})))
;; We can use multiple input sets.
(is (= #{"Java" "Clojure" "Groovy" "Kotlin"}
(set/union #{"Java" "Clojure" "Groovy"}
#{"Groovy" "Clojure"}
#{"Kotlin"})))
;; A nil input is ignored.
(is (= #{"Clojure" "Groovy" "Kotlin"}
(set/union #{"Groovy" "Clojure"} nil #{"Kotlin"})))
Written with Clojure 1.10.1.
Original post written on July 2, 2020
Find Differences In Sets
If we want to get the values from a set that are not part of one or more other sets we must use the difference
function in the clojure.set
namespace. The function returns a set with all values from the first set that are different from values in other sets.
In the following example we use the difference
with several sets:
(ns mrhaki.set.difference
(:require [clojure.set :as set]
[clojure.test :refer [is]]))
;; The difference function will take a first set
;; and leave out elements that are in the following set(s).
(is (= #{"Java"}
(set/difference #{"Java" "Clojure" "Groovy"}
#{"Kotlin" "Groovy" "Clojure"})))
(is (= #{"Java"}
(set/difference #{"Java" "Clojure" "Groovy"}
#{"Kotlin" "Groovy"}
#{"Clojure"})))
;; When other sets do not contain values
;; from the first set, the result is the original set.
(is (= #{1 2 3}
(set/difference #{1 2 3} #{4 5})))
Written with Clojure 1.10.1.
Original post written on June 29, 2020
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.
Original post written on October 30, 2022
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.
Original post written on February 8, 2021
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.
Original post written on February 5, 2021
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.
Original post written on November 2, 2020
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.
Original post written on October 11, 2020
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.
Original post written on October 6, 2020
Taking Or Dropping Elements From A Collection Based On Predicate
In Clojure we can take or drop elements from a collection based on a predicate using the functions take-while
and drop-while
. With the function take-while
we take elements as long as the predicate returns true
. Once the predicate returns false
the function stops returning elements. Using the function drop-while
we skip elements in the collection if the predicate returns true
. If the predicate returns false
the remaining elements in the collection are returned.
In the following example we use take-while
and drop-while
with different collection types:
(ns mrhaki.seq.take-while
(:require [clojure.test :refer [is]]
[clojure.string :refer [join]]))
;; Simple range of numbers to 10 to invoke
;; take-while and drop-while functions.
(def numbers (range 10))
;; Use take-while to get all number as long as
;; the number is less than 5.
(is (= [0 1 2 3 4] (take-while #(< % 5) numbers)))
;; Use drop-while to skip all numbers that are
;; less than 5, so we get all numbers from 5.
(is (= [5 6 7 8 9] (drop-while #(< % 5) numbers)))
;; String is a collection of characters so
;; we can use take-while and drop-while.
(def s "Clojure Rocks!")
(is (= "Clojure "
(join (take-while #(not= \R %) s))))
(is (= "Rocks!"
(join (drop-while #(not= \R %) s))))
;; A map structure is a collection of key/value vectors,
;; so take-while and drop-while can be used.
(def user {:name "mrhaki" :loves "Clojure" :worksAt "JDriven"})
;; Helper function to return the length of a keyword.
(defn keyword-length
"Returns length of keyword."
[entry]
(.length (name (key entry))))
(is (= {:name "mrhaki"}
(into {} (take-while #(= 4 (keyword-length %)) user))))
(is (= {:loves "Clojure" :worksAt "JDriven"}
(into {} (drop-while #(= (key %) :name) user))))
Written with Clojure 1.10.1.
Original post written on July 15, 2020
Partition Collection Into Sequences
Clojure has the partition
, partition-all
and partition-by
functions to transform a collection into a list of sequences with a (fixed) number of items. We can set the number of items in each sequence by providing a number as the first argument of the partition
and partition-all
functions. Any remainder elements are not in the resulting list of sequences when we use partition
, but are when we use partition-all
. We can also specify another collection to use values from to fill up the remainder as the third argument of the partition
function.\
Optionally we can specify an offset step value as a second argument using both functions. This mean a new partition sequence will start based on stepping through the original collection with the given step value.\
Finally we can use a function to define when a new partition must start with the partition-by
function. Every time the function returns a new value a new partition will begin.
In the following example Clojure code we use all three functions with all possible arguments:
(ns mrhaki.core.partition
(:require [clojure.test :refer [is]]))
;; Sample string (a sequence of characters).
(def letters "aBCdeFg")
;; First argument defines how many items are in each partition.
;; Any remainder is ignored.
(is (= [[\a \B] [\C \d] [\e \F]] (partition 2 letters)))
;; With partition-all the remainder is part of the result.
(is (= [[\a \B] [\C \d] [\e \F] [\g]] (partition-all 2 letters)))
;; The second argument is a step offset.
(is (= [[\a \B] [\d \e]] (partition 2 3 letters)))
(is (= [[\a \B] [\d \e] [\g]] (partition-all 2 3 letters)))
(is (= [[\a \B \C] [\C \d \e] [\e \F \g]] (partition 3 2 letters)))
(is (= [[\a \B \C] [\C \d \e] [\e \F \g] [\g]] (partition-all 3 2 letters)))
;; The third argument is used to fill the last remainder partition if needed.
(is (= [[\a \B \C] [\d \e \F] [\g \! \?]] (partition 3 3 [\! \? \@] letters)))
(is (= [[\a \B \C] [\d \e \F] [\g \! \!]] (partition 3 3 (repeat \!) letters)))
;; When padding collection has not enough items, only what is available
;; is used to fill the remainder part.
(is (= [[\a \B \C] [\d \e \F] [\g \!]] (partition 3 3 [\!] letters)))
;; Using partition-by we can use a function that perfoms the split
;; when the function returns a new value.
(is (= [[\a] [\B \C] [\d \e] [\F] [\g]]
(partition-by #(Character/isUpperCase %) letters)))
(is (= [[ 1 2 3 4] [5] [6 7 8 9] [10] [11 12 13 14]]
(partition-by #(= 0 (mod % 5)) (range 1 15))))
Written with Clojure 1.10.1.
Original post written on April 28, 2020
Counting Frequency Of Items In A Collection
If we want to know how often an item is part of a collection we can use the frequencies
function. This function returns a map where each entry has the item as key and the number of times it appears in the list as value.
In the following example Clojure code we use the frequencies
function:
(ns mrhaki.core.frequencies
(:require [clojure.test :refer [are is]]))
(def sample "Clojure is cool!")
(is (= {\space 2 \! 1 \C 1 \c 1 \e 1 \i 1 \j 1 \l 2 \o 3 \r 1 \s 1 \u 1}
(frequencies sample))
"Frequency of each character in sample")
(def list ["Clojure" "Groovy" "Cool" "Goodness"])
(is (= {\C 2 \G 2}
(frequencies (map first list)))
"Two words start with C and two with G")
(def numbers '(29 31 42 12 8 73 46))
(defn even-or-odd
"Return string even when number is even,
otherwise return string odd."
[n]
(if (even? n)
"even"
"odd"))
(is (= {"odd" 3 "even" 4}
(frequencies (map even-or-odd numbers)))
"list numbers has 3 odd and 4 even numbers")
(def user {:user "mrhaki" :city "Tilburg" :age 46})
(is (= {java.lang.String 2 java.lang.Long 1}
(frequencies (map type (vals user))))
"user map has two values of type String and 1 of type Long")
Written with Clojure 1.10.1.
Original post written on April 24, 2020
Reapply Function With Iterate To Create Infinite Sequence
The iterate
function create a lazy, infinite sequence based on function calls. The iterate
function takes a function and an initial value as arguments. The first element in the sequence is the initial value, next the function is invoked with the previous element as argument and this continues for each new element. Suppose we have a function #(+ 2 %)
that adds 2
to the input argument. Then if we use this function with iterate
and start with value 1
the first elements of the sequence will be 1
, (+ 2 1)
, (+ 2 3)
, (+ 2 5)
. So first element is the initial value, next element is the invocation of the function with input argument 1
. The result of this function is 3
, which is then the input for the function to calculate the next element and so on.
In the following example code we use iterate
in different scenario's:
(ns mrhaki.core.iterate
(:require [clojure.test :refer [is]]))
;; Lazy sequence of numbers in steps of 2.
(def odds (iterate #(+ 2 %) 1))
(is (= (list 1 3 5 7 9 11 13 15 17 19)
(take 10 odds)))
;; Define lazy sequence with a growing string.
;; The first element is ar, next argh, then arghgh etc.
(def pirate (iterate #(str % "gh") "ar"))
(def crumpy-pirate (nth pirate 5))
(is (= "arghghghghgh" crumpy-pirate))
;; Function that returns the given amount
;; plus interest of 1.25%.
(defn cumulative-interest
[amount]
(+ amount (* 0.0125 amount)))
;; Lazy sequence where each entry is the
;; cumulative amount with interest based
;; on the previous entry.
;; We start our savings at 500.
(def savings (iterate cumulative-interest 500))
;; After 5 years we have:
(is (= 532.0410768127441
(nth savings 5)))
;; Function to double a given integer
;; and return as bigint.
(defn doubler [n] (bigint (+ n n)))
;; Define lazy infinite sequence
;; where each element is the doubled value
;; of the previous element.
(def wheat-chessboard (iterate doubler 1))
;; First elements are still small.
(is (= (list 1 2 4 8 16 32)
(take 6 wheat-chessboard)))
;; Later the elements grow much bigger.
(is (= (list 4611686018427387904N 9223372036854775808N)
(->> wheat-chessboard (drop 62) (take 2))))
;; Sum of all values for all chessboard squares
;; is an impressive number.
(is (= 18446744073709551615N
(reduce + (take 64 wheat-chessboard))))
Written with Clojure 1.10.1.
Original post written on April 16, 2020
Checking Predicate For Every Or Any Element In A Collection
In Clojure we can use several functions to see if at least one or all elements in a collection return true
or false
for a predicate. The function every?
only returns true
if the predicate function returns true
for all elements in the collection. To function not-every?
return true
if a predicate function return false
for all elements in a collection. The some
function is a bit different (notice there is no ?
) and returns the first logical true
value from the predicate function for the elements in the collection. So the return type of the predicate doesn't have to be a Boolean
value and then the return type of some
is also not a Boolean
. If the predicate returns a Boolean
value we can use some
like a any
function (any
is not part of Clojure). Clojure provides a not-any?
function that returns true
if the predicate function returns false
for one element in the collection and false
otherwise.
The following example uses the different functions on a vector with some cartoon names:
(ns mrhaki.seq.pred
(:require [clojure.test :refer [is]]
[clojure.string :as str]))
;; Vector of toons to check predicates on.
(def toons ["Daffy Duck" "Bugs Bunny" "Elmer Fudd" "Yosemite Sam"])
;; Helper function to count number of names.
(defn count-names
[name]
(count (str/split name #" ")))
;; Every toon has two names.
(is (true? (every? #(= 2 (count-names %)) toons)))
;; Not every toon name starts with "A".
(is (true? (not-every? #(str/starts-with? "A" %) toons)))
;; Helper function to check if the first letter
;; of both names is the same.
(defn same-first-letters?
[name]
(let [names (str/split name #" ")
first-letter (first (first names))
second-letter (first (second names))]
(= first-letter second-letter)))
;; Some toons have the same first letter
;; for their first and last name.
(is (true? (some same-first-letters? toons)))
;; Using set as function to check toon is in the toons vector.
;; Notice some function return the first value from the predicate function
;; that is not nil or false, instead of a boolean like with
;; every?, not-every? and not-any?.
(is (not (true? (some #{"Yosemite Sam", "Road Runner"} toons))))
(is (= "Yosemite Sam" (some #{"Yosemite Sam", "Road Runner"} toons)))
;; As seen on https://clojuredocs.org/clojure.core/any_q a
;; possible implementation for any that returns true or false.
(defn any [pred coll] ((comp boolean some) pred coll))
(is (true? (any #{"Yosemite Sam", "Road Runner"} toons)))
;; There is a toon name where their name length is 10.
(is (false? (not-any? #(= (count %) 10) toons)))
Written with Clojure 1.10.1.
Original post written on April 8, 2020
Keep Non-Nil Function Results From Collection
The keep
function in Clojure invokes a function on each item in a collection and only returns non-nil results from the function invocation. The result of the keep
function is a lazy sequence.
The following example uses the keep
function, but also show what results would be when using map
function on the same collection with the same function argument:
(ns mrhaki.seq.keep
(:require [clojure.test :refer [is]]))
(def stuff ["Clojure" "Groovy" "Java"])
;; Function keep filters on non-nil results that are returned
;; from applying the function.
(is (= '("Clojure has more than 6 characters")
(keep #(if (> (count %) 6) (str % " has more than 6 characters")) stuff)))
;; Using the same function with map shows the
;; nil results that are filtered by keep.
(is (= (list "Clojure has more than 6 characters" nil nil)
(map #(if (< 6 (count %)) (str % " has more than 6 characters")) stuff)))
(def user {:name "Hubert" :nickname "mrhaki" :age 46})
;; Returned result from the function is a boolean
;; so it is always included in the result after applying
;; the keep function.
(is (= [true true false]
(keep #(instance? String (% 1)) user)))
;; Here the function returns a string result or nill.
(is (= [":name has a String value" ":nickname has a String value"]
(keep (fn [[k v]] (if (instance? String v) (str k " has a String value"))) user)))
Written with Clojure 1.10.1.
Original post written on April 2, 2020
Flatten Collections
We can use the flatten
function when we have a collection with nested sequential collections as elements and create a new sequence with the elements from all nested collections.
In the following example we use the flatten
function:
(ns mrhaki.sample
(:require [clojure.test :refer [is]]))
;; Elements from nested sequential collections are flattend into new sequence.
(is (= [1 2 3 4 5] (flatten [1 [2 3] [[4]] 5])))
(is (sequential? (flatten [1 [2 3] [[4]] 5])))
(is (= [1 2 3 4 5] (flatten [[1] [2 3] [[4 5]]])))
;; We can use different sequential collection types.
;; We might have to force a type to a sequential collection with seq.
(is (= '(1 2 3 4 5) (flatten [1 (seq (java.util.List/of 2 3)) ['(4 5)]])))
(is (= (quote (1 2 3 4 5)) (flatten [[1] [(range 2 6)]])))
;; flatten on nil returns empty sequence.
(is (= () (flatten nil)))
Written with Clojure 1.10.1.
Original post written on January 10, 2020
Getting Intersections Between Sets
In the clojure.set
namespace we can find the intersection
function. This functions accepts one or more sets as arguments and return a new set with all elements that are present in the sets that are passed as arguments to the intersection
function. The argument must be a set, so we need to convert other collection or seq values to a set first before we use it as an argument for the function.
In the following example we use one, two or three arguments for the intersection
function and also convert other types to a set to be used as argument:
(ns mrhaki.sample
(:require [clojure.set :refer [intersection]]
[clojure.test :refer [is]]))
;; Use intersection with sets to find common elements.
(is (= #{"Clojure"} (intersection #{"Java" "Scala" "Clojure"} #{"Clojure" "Groovy"})))
;; An empty set is returned if there is no common element.
(is (= #{} (intersection #{"Java" "Groovy" "Clojure"} #{"C++" "C#"})))
;; We can use more than two sets to find intersections.
(is (= #{"Clojure"} (intersection #{"Java" "Scala" "Clojure"}
#{"Clojure" "Groovy"}
#{"Groovy" "JRuby" "Clojure"})))
;; With one set intersections returns the set.
(is (= #{"Clojure" "Groovy"} (intersection #{"Clojure" "Groovy"})))
;; Only sets are allowed as arguments for the intersection function.
;; If one of the arguments is not a set the return value is unexpected.
(is (= #{} (intersection #{"Clojure" "Groovy"} ["Java" "Scala" "Clojure"])))
;; But we can convert a non-set to a set with the set function.
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} (set ["Java" "Scala" "Clojure"]))))
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} (set '("Java" "Scala" "Clojure")))))
;; Or using into #{}.
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"}
(into #{} (vals {:platform "Java" :language "Clojure"})))))
Written with Clojure 1.10.1
Original post written on January 8, 2020
Files
Writing Text File Content With spit
In a previous post we learned how to read text file contents with the slurp
function. To write text file content we use the spit
function. We content is defined in the second argument of the function. The first argument allows several types that will turn into a Writer
object used for writing the content to. 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 Writer
, BufferedWriter
, OutputStream
, URI
, URL
and Socket
. As an option we can specify the encoding used to write the file content using the :encoding
keyword. The default encoding is UTF-8 if we don't specify the encoding option. With the option :append
we can define if content needs to be appended to an existing file or the content should overwrite existing content in the file.
In the following example we use the spit
function with several types for the first argument:
(ns mrhaki.sample.spit
(:import (java.io FileWriter FilterWriter StringWriter File)))
;; With spit we can write string content to a file.
;; spit treats the first argument as file name if it is a string.
(spit "files/data/output.txt" (println-str "Clojure rocks!"))
;; We can add the option :append if we want to add text
;; to a file, instead of overwriting the content of a file.
(spit "files/data/output.txt" (println-str "And makes the JVM functional.") :append true)
;; Another option is the :encoding option which is UTF-8 by default
(spit "files/data/output.txt" (str "Clojure rocks!") :encoding "UTF-8")
;; The first argument can also be a File.
(spit (File. "files/data/file.xt") "Sample")
;; We can pass a writer we create ourselves as well.
(spit (FileWriter. "files/data/output.txt" true) "Clojure rocks")
;; Or use a URL or URI instance.
(spit (new java.net.URL "file:files/data/url.txt") "So many options...")
(spit (java.net.URI/create "file:files/data/url.txt") "And they all work!" :append true)
Written with Clojure 1.11.1.
Original post written on October 1, 2022
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.
Original post written on September 27, 2022
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.
Original post written on September 27, 2021
Java Interoperability
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.
Original post written on March 18, 2021
Create And Initialize Object Based On Java Class With doto
It is very easy to work with Java classes in Clojure. If we want to create a new object based on a Java class and invoke methods to initialize the object directly we can use the doto
macro. The first argument is an expression to create a new object and the rest of the arguments are functions to invoke methods on the newly created object. The object returned from the first argument is passed as first argument to the method invocations. The doto
function returns the object that is created with the first argument.
In the following example code we use the doto
function in several cases:
(ns mrhaki.core.doto
(:require [clojure.test :refer [is]]))
;; With doto we can invoke functions on object returned
;; by the first argument, where the object is passed
;; before the given arguments.
(def sb (doto (StringBuilder.)
(.append "one")
(.append "two")
(.reverse)))
(is (= "owteno" (.toString sb)))
;; We can use functions in doto. For example
;; to create a value for the function invocation
;; on the type of the first argument.
(def sample (doto (new StringBuilder)
(.append "{")
(.append (apply str (repeat 10 "a")))
(.append "}")))
(is (= "{aaaaaaaaaa}" (.toString sample)))
;; Type returned is the same as result of evaluation
;; of first argument, not the last argument.
(is (instance? StringBuilder (doto (StringBuilder.) (.toString))))
Written with Clojure 1.10.1.
Original post written on July 13, 2020
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.
Original post written on February 3, 2021
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.
Original post written on January 28, 2021
Turn Java Object To Map With bean Function
The map data structure is used a lot in Clojure. When we want to use Java objects in our Clojure code we can convert the Java object to a map with the bean
function. This function will use reflection to get all the properties of the Java object and converts each property with the property value to a key with value in the resulting map. The bean
function will not recursively convert nested objects to a map, only the top-level properties are turned into key value pairs.
We see several examples of using the bean
function in the following code snippet:
(ns mrhaki.core.bean
(:require [clojure.test :refer [is]])
(:import (java.net URI)
(mrhaki.java Person Address)))
(is (= {:path "",
:rawQuery "search=clojure",
:fragment nil,
:authority "www.mrhaki.com",
:rawAuthority "www.mrhaki.com",
:port -1,
:absolute true,
:host "www.mrhaki.com",
:rawPath "",
:opaque false,
:rawSchemeSpecificPart "//www.mrhaki.com?search=clojure",
:class java.net.URI,
:rawUserInfo nil,
:query "search=clojure",
:rawFragment nil,
:scheme "https",
:userInfo nil,
:schemeSpecificPart "//www.mrhaki.com?search=clojure"}
(bean (URI. "https://www.mrhaki.com?search=clojure"))))
(comment "For the next sample we use a Java class Person
with properties name and alias of type String and
a third property of type Address. The Java class Address
has a single String property city.
Pseudo code:
class Person { String name, alias; Address address; }
class Address { String city; }")
(def person (Person. "Hubert" "mrhaki" (Address. "Tilburg")))
(is (= {:name "Hubert" :alias "mrhaki"}
(select-keys (bean person) [:name :alias])))
;; Properties with custom classes are not automatically
;; also converted to a map representation.
(is (instance? mrhaki.java.Address (:address (bean person))))
(is (= {:city "Tilburg"}
(select-keys (bean (:address (bean person))) [:city])))
(is (= {:address {:city "Tilburg", :class mrhaki.java.Address},
:alias "mrhaki",
:class mrhaki.java.Person,
:name "Hubert"}
(assoc (bean person) :address (bean (.getAddress person)))))
Written with Clojure 1.10.1.
Original post written on July 14, 2020