Clojure at a Glance — A Language a Day, Advent Calendar Day 14/24

About this ‘A Language a Day’ Advent Calendar

Andrew Shitov. A Language a Day

This article became a part of my book ‘A Language a Day’, which you can get in both electronic and paper format. Languages covered in the book: C++, Clojure, Crystal, D, Dart, Elixir, Factor, Go, Hack, Hy, Io, Julia, Kotlin, Lua, Mercury, Nim, OCaml, Raku, Rust, Scala, and TypeScript.

Learn more where to get the book

Welcome to Day 14 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the Clojure programming language.

Facts about the language

Some facts about Clojure:

  • Based on Lisp
  • Functional programming support
  • Targets Java platform
  • Appeared in 2007
  • Website: clojure.org

Installing and running Clojure

The website of the language gives instructions for installing it on different operating system. For the Mac users, run:

$ brew install clojure

You will have a command-line tool that you can run a program with:

$ clj hello-world.clj

Hello, World!

Clojure has a specific syntax, and a simple call of a function is a list:

(println "Hello, World!")

Here, the list contains two items: the name of the println function and its argument, the string containing the message.

Variables

You bind a value to a name using the def keyword. To do that, construct a list like shown below:

(def who "John")
(println (str "Hello, " who "!")) ; Hello, John!

The first line creates a variable who, which is used in the second line where it is printed. You could pass all three parts of the message to the print function: (print "Hello," who "!"), but in this case Clojure will print them space-separated. So, it is better to construct a new string by calling str with those three arguments how this is done in the above example.

Lists and vectors

To create a list, use the list constructor:

(def data (list 10 20 30))
(doseq [n data]
    (println n))

This program also prints the elements of a list by calling doseq, which is equivalent to the for loop but allows side effects (remember that printing something is a side effect in functional languages).

Vectors allow random access and they are created with a pair of brackets. To access the nth element, use the nth function:

(def data [11 22 33])
(doseq [n data]
    (println n))

(println (nth data 2)) ; prints 33

You can also use nth with lists. Vectors are kept in memory in such a way that you access their elements in O(1) time, while for lists it is O(n).

Functions

You define functions with the defn keyword. You can see the syntax in the following example:

(defn greet [name]
    (println (str "Hello, " name "!")))

(greet "John")
(greet "Alla")

Function’s arguments follow the function name and are placed in brackets. If you don’t need any arguments, leave them empty:

(defn f []
    (println "I am a function."))

(f)

Lambdas

You can define an anonymous function with the fn keyword. Here is an example of using such a function inside a map call that transforms a list of integers to a list of their squares:

(def data (list 1 3 5))

(def transformed_data (
    map (fn [x] (* x x)) data))

(print transformed_data) ; (1 9 25)

Factorial, solution 1

Let us first solve the factorial task by using recursion. We can define a function that calls itself.

(defn factorial [n] (
    if (< n 2) 1 (* n (factorial(- n 1)))
))

(println (factorial 1)) ; 1
(println (factorial 5)) ; 120
(println (factorial 7)) ; 5040

Note that to compare the two values you have to write a list (< n 2) and not an expression like n < 2. The same for decrementing the value of n: (- n 1).

Factorial, solution 2

Alternatively, use the apply function that applies the operator to a list of values. To have the list, we need to create a range from 2 to n, including n.

(defn factorial [n] (
    apply * (range 2 (+ n 1)) ))

In the above code, you can alternatively use the dec and inc functions to get a decremented or incremented value: (dec n) and (inc n). Notice that they do not change the value of the variable, but only return the result:

(def n 10)

(println (dec n)) ; 9
(println n) ; did not change: 10

(println (inc n)); 11
(println n) ; still 10

Multimethods

There is no object-oriented support in Clojure, but if we need to talk about polymorphic behaviour, we could use multimethods. Let us examine a small program that implements another version of the factorial.

The program below contains three pre-defined solutions for the values of the factorial of 0, 1, and for the rest numbers.

(defmulti factorial identity)

(defmethod factorial 0 [_] 1)
(defmethod factorial 1 [_] 1)
(defmethod factorial :default [n] 
    (* n (factorial (- n 1))))

You announce a multimethod function with the defmulti keyword, and then provide the implementations using defmethod. In our example, the dispatch is based directly on values, but instead of identity(that returns the passed value, n in our case) we could create a separate dispatch function (see the next example).

The first two factorial variants respond to the exact values 0 and 1, and the third responds to :default, thus it handles all other situations. The [_] construct tells that we do not need any arguments for this variant.

A Polymorphic example

In this example, we are using the multimethods, but the dispatching is done via an anonymous function. The animals are the maps (this is a datatype similar to dictionaries or hashes in other languages) with the key kind representing the animal type and nickname for the name. The dispatch function converts such objects to the value corresponding to its kind key.

Here is the full code:

(defmulti info (fn [x] (x "kind")))

(defmethod info :dog [x] (str (x "nickname") " is a dog."))
(defmethod info :cat [x] (str (x "nickname") " is a cat."))

(def zoo (list
    {"kind" :dog "nickname" "Charlie"}
    {"kind" :dog "nickname" "Milo"}
    {"kind" :cat "nickname" "Gracie"}
    {"kind" :cat "nickname" "Molly"}
))

(doseq [x zoo]
    (println (info x)))

In the loop, the x variable stands for one of the animals, for example: {"kind" :dog "nickname" "Charlie"}. This value is passed to the info function, the :dog value is extracted for it, and based on this, the correct multi-function is triggered.

Concurrent computing

Clojure targets to work with JVM, and a number of its features rely on the libraries that you have in Java. If you want to explore concurrency deeper, you should probably start with the overview on Clojure’s website and examine the core.async library.

Sleep Sort

The Sleep sort implementation presented below uses futures, thus you don’t need to import any Java-specific libraries to your code.

(defn sort-me [n]
    (Thread/sleep (* n 1000))
    (println n)
)

(def data [10 4 2 6 2 7 1 3])

(doseq [n data]
    (future (sort-me n)))

(shutdown-agents)

The main loop over data creates a future that executes the sort-me function with the given value. That function, in its turn, performs a delay (using the Thread/sleep function) and prints the number. To terminate the program, you need to call shutdown-agents.

Notice that dashes are allowed characters in identifiers, and the slash is used to add the namespace to the name.

Get more

When learning Clojure, you may be confused how simple it looks in the beginning when you understand the main idea behind parentheses, but then you may be confused how difficult it is when you will have to count them 🙂

Examine the following resources to get more information about the language:

The source codes for today’s article are available on GitHub.

Next: Day 15. Nim

One thought on “Clojure at a Glance — A Language a Day, Advent Calendar Day 14/24”

Leave a Reply

Your email address will not be published. Required fields are marked *

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code