Clojure

Cloj ure
Alexander Sutherland - tfy11asd
Olov L˚
angstro¨m - ens10olm
Hugo K¨allstr¨om - Oi11hkm
Johan Nilsson - C11jnn
17 februari 2014
1
1
Introduction
Origin
Although the exact point Clojure came into existence is hard to pinpoint,
it is estimated that Clojure came to be around 2007 by the hands of Rick
Hickey. Hickey wanted to create a modernized Lisp dialect for functional programming something that was sorely lacking in that day and age.
His first project, dotLisp, was based off of the .NET framework but he found
the software far too primitive and moved on to create a Lisp that targeted
the Java Virtual Machine(JVM) as opposed to the .NET framework. Into
this system he would include concurrency based design, a deeply symbiotic
relationship with the JVM and a strong aversion to object orientation, at
least in its more common sense, concluding in the formation of Clojure.
Lisp itself is a functional programming language and Clojure shares many of
it’s traits such as having code as data allowing a dynamic structure, symbolic
expressions and a polish-prefix notation system.
Philosophy
Hickey himself has described the language as “An effort in pragmatic dynamic language design”, essentially an attempt to standardise and simplify
the advanced and elegant dynamic languages into something more stable
that could be used in the industry without risks involved for the company
using the software. Many companies try to avoid using unusual programming
languages as maintenance and problems that arise during development become far more complicated. This is where Clojures inherent closeness to the
JVM gave it a certain stability that more alien languages would have trouble
matching.
Clojure being designed as a modern functional programming with Java integration is an attempt to unify the fairly scattered array of Lisp dialects
that exist in this day and age. It could be said that the lack of unification
between dialects is why the industry is so averse to using functional languages, at least in terms of Lisp. With this being said Clojure tries to avoid
object orientation with Hickey stating that standard object-oriented builds
are inherently imperative and therefore unfit for use in a functional environment with the added note that it was originally used for simulation and is
2
now used for everything regardless of practicality.
Concurrency is also an important part of Clojures design. Immutable data
types and avoiding object-oriented constructions let the language avoid many of the pitfalls that standard imperative languages suffer from in terms of
thread safety. Polymorphism in Clojure allows for a highly flexible function
system having a special transactional memory and “agents” to handle the
task of identifying data types.
On a side note some of the more elegant features Clojure retains from Lisp
are code-as-data and data-as-code capabilities which essentially allow a user
to code their own sub-language that is handled a specific way by the system.
This may not sound like it would cause you to snap your keyboard in sheer
excitement but it in truth gives the user an incredible amount of leeway when
it comes to structuring the program.
A small pile of not very discrete books could be written about the conceptuality of a dynamic programming language but a summarized gist of the matter
will be provided here. It allows a program to expand and change itself during it’s runtime. Dynamic languages are also commonly known as scripting
languages, which is at times false as this is only true in certain circumstances.
3
2
Syntax and some examples
As mentioned above Clojure uses prefix-polish notation, both for arithmetic
operations and in other expressions. Furthermore, all code in Clojure is written in lists, which is done by writing parenthesis around every expression.
With this in mind we can start writing some simple expressions:
Arithmetic operations:
- (+ 2 2) calculates 2 + 2
- (- 2 2) calculates 2 - 2
- (* 2 2) calculates 2 * 2
- (/ 2 2) calculates 2 / 2
All of the above operations can be calculated with both integers and floating
point numbers. Clojure also has a datatype for rational numbers which can
be used all of the operations as well. For example, the expression (/ 1 3) will
be evaluated to 1/3. (/ 1 3.0) can be used instead if a floating point result is
preferred and will be evaluated as 0.3333...
The prefix-polish notation is used in other expressions as well, for example,
the if-statement which checks if the number 4 is smaller than the number 10
is in Clojure written as:
(if ( < 4 10) (code to be executed if 4 is less than 10))
Besides the different ways of representing numbers, Clojure also contains all
of the other usual datatypes, like chars, strings, boolean values etc. Four other datatypes which are used a lot in Clojure are lists, vectors, sets and maps.
- A List can be created by writing a ‘ followed by the elements of the list
inside parenthesise, for example ‘(1 2 3) creates a list with the elements 1, 2
and 3.
- Vectors are created by writing the elements of the vector inside of brackets,
for example [1 2 3].
- Sets are created by writing a # followed by the elements of the set inside
curly brackets, for example #1 2 3.
- A map with the keys ‘green’ and ‘red’, with the corresponding values ‘apple’
and ‘cherry’ can be created by writing {:green :apple, :red :cherry}.
4
In Clojure, different functions are used instead of using loops. For example,
if we want to increase every value in the vector [1 2 3] with 1 we can use the
function map. This can be done with the following piece of code:
(map inc [1 2 3])
Map takes a function and applies it to every value of the vector and inc
increases a value by one.
Names of variables can be defined by using def.
(def vec [1 2 3])
will define the name “vec” to refer to the vector [1 2 3] and after writing that
line of code, the name vec can be used instead of writing the vector directly.
For example, (map inc vec) will now give the same result as writing (map
inc [1 2 3]).
Functions can also be defined by using def in the same way as when defining
names for variables. To created a function called “square” which takes a
single number and squares it we write:
(def square (fn [x] (* x x)))
This creates a function with a single value x as input parameter and calculates the square of this number by multiplying it with itself, (* x x). This
function is also given the name “square”, and this can be used to use the
function. For example, running (square 10) will give the result 100.
Defining functions is a very common task which is why it also has a shorter
syntax to write the same thing. The following code will define the same
function as above, but in a more compact way:
(defn square [x] (* x x))
As another example of creation of functions, a classic “Hello, World!”-function
can be defined with the following code:
(defn hello [] (println “Hello, World!”))
5
This creates a function called “hello” which takes no input (hence the empty
vector []) and when executed will print the text “Hello, World!”.
As mentioned above, Clojure is built upon Java. This means among other
things that we can use various classes from Java libraries. If we for example
want to show our “Hello, World!” text in a pop up window instead of printing
it to the terminal we can use the class JOptionPane by writing:
(javax.swing.JOptionpane/showMessageDialog nil “Hello, World!”)
6
3
Defining features
Concurrency
Clojure is designed around concurrent programming and has several methods
for dealing with the problems that usually occur around it. The primary design choice is immutability, which means that the language’s data structures
cannot be modified. This allows keeping multiple references to the same object without worrying about the state changing from another place or thread.
Immutability also minimizes side-effects and makes it easier to reason about
the code when functions can be called with the same input repeatedly and
always give the same result back.
However, sometimes modifying state is hard to avoid and Clojure does this
in a way where it can still maintain thread safety. The language has the datatypes atom, ref and agent which are ways to maintain mutable state in a
safe way. They can all be seen as wrappers around a value and the value is
accessed by calling @ on the type.
Atom is the simplest of the three, and it can be compared to a regular variable in imperative languages. It differs in that changing it is done atomically,
meaning that if the atom is modified from another source while trying to set
the value, the operation is restarted with the modified value as parameter.
The operations for it are atom, swap!, reset! and compare-and-set!.
An atom is created by calling the function atom and passing as argument
the value it should contain. The other operations are for modifying the contained value, swap! for applying a function to the value and saving it, reset!
for setting it to a fixed value and compare-and-set! for comparing against a
specified value first and only changing it if they match.
Atom example:
(def a (atom 5)) ;defines an atom with initial value 5
(swap! a inc) ;swaps current value in a with a + 1
(println @a) ;Prints the value that a contains
Ref can be seen as an extension of the atom. It is also changed atomically
but the difference is that it is changed in a “transaction”, which can be seen
as a series of function calls which can change the state of the ref. They must
all succeed in order for the value to actually change. It can be compared to a
7
bank transaction, where money should be withdrawn from one account and
deposited to another. If the transaction fails it would be catastrophic if the
money had been withdrawn from the first account but never deposited to the
other.
Agent is similar to the atom in that it signifies a value, but the difference is
that modifying it is done asynchronously on another thread. This is useful
if changing the value can take a long time but you want the program to
continue running and accessing the old value in the meantime. For example
if the agent is a RSS feed, fetching an updated list can be slow depending
on the connection but the user might still want to read the old articles while
the update is happening. Dereferencing an agent will give back the last value
that it was set, even if it is currently trying to change it.
Java Interop
An important part of Clojure is the capability to call Java code seamlessly.
It allows the use of any libraries that exist in Java to be used in Clojure
without modification.
Macros
Being a Lisp, Clojure also has the means to create macros which can change
the program structure itself. This is largely due to the fact that the program
is very homogeneous, lists and function calls are pretty much the same.
Macros can be hard to understand but they are essentially a way to selectively
evaluate parts of the code and then pass it along to be executed. It is best
understood with an example:
(defmacro do-twice [action]
‘(do action action))
This code declares a macro called do-twice which takes a parameter called
action. The symbol ‘ means that the following code is quoted, and should be
passed along as-is. The symbol is used to unquote something inside a quote
block. It can be seen as the argument being pasted inside the do function
twice and then executed. See it in action in the following code.
(do-twice (println “Hello!”))
(do (println “Hello!”) (println “Hello!”)) ;after expansion
8
If we would try to write this as a regular function we would run into the
problem of println being evaluated first, printing “Hello!” then returning nil,
resulting in do-twice being called with nil.
(defn do-twice [action]
‘(do ∼action ∼action))
(do-twice (println “Hello!”))
(do nil nil) ;after expansion
9
4
Pros and cons
Clojure is a really powerful language, but as with all programming languages
it isn’t perfect. The fact that Clojure runs on the JVM makes it very diverse,
it can even utilize all of Java’s libraries, but it comes with a catch. Since
compilation errors can turn out to be very cryptic and difficult to read, one
needs some fundamental understanding of Java, which of course is a completely different language. This should not be a problem for most programmers
since Java is so widespread and common, but it is nonetheless a drawback
for Clojure.
Knowing Java is not the only prerequisite for using Clojure efficiently. Since
LISP is another major language for which Clojure is based upon the user
should have at least some familiarity with it, because the syntax of LISP
andi Clojure are quite similar. The first thing to notice is how much Clojure
uses parentheses, this can seem daunting at first but is in fact a good way
of structuring code. Because of the parentheses one can think about code in
terms of expressions instead of “lines” like imperative languages use. This
also makes the code more readable.
Even though Clojure runs on the JVM it has very little in common with
Java. The creator of Clojure, Ricky Hickey, has taken a standpoint against
object oriented programming. He thinks that OOP is used even when it’s
not necessary and that functional languages are more appropriate for many
problems. Another problem with OOP is the concurrency problems it introduces, this is not a problem with functional languages because of the way it
uses immutable objects. Clojure is therefore very well suited for multithreaded programming, and it is built in a way so that the programmer doesn’t
have to think about concurrency issues. Even though there is great support
for multithreading, this doesn’t come without complications. If a big program runs Clojure this can be a quite demanding process which utilizes a
lot of memory. It can also be ineffective if many objects needs to be updated
frequently, because of the way immutable objects work one needs to create
new objects instead of changing the existing one.
Macros is another of Clojures many great features. These can be very powerful and transform code to things one didn’t think was possible. But even
though macros are one of the more powerful tools in Clojure, they aren’t
10
the easiest to use. Macros are prone to errors and can be quite difficult to
debug because of the way they manipulate the code. But if macros are used
correctly one can extend the core features of Clojure to almost anything.
(A great example is the use of a for-loop macro. Since Clojure is a functional
language loops doesn’t exist, but with macros one can add this feature (if this
should be done is another question, the point is that it can be done).)
5
Conclusions
So, will Clojure grow and become a mainstream language like Java and Python? Functional programming has been around for a long time but it still
hasn’t become as big as the familiar imperative style. With multi-core processors being the standard in consumer electronics it would not seem that
surprising to see functional programming getting more attention in the near
future. Because of the way Clojure handles concurrency it is very suitable
for this kind of problem.
The flexibility of macros and polymorphism makes Clojure such a diverse
language. This means that users themselves can extend the language when
needed, instead of waiting for official updates. Dynamic programming also
makes it easier to reuse code, and the developer can build his own library of
modules which then can be used in lots of different situations.
The JVM also makes up for the great flexibility of Clojure, the JVM is very
widely used today and therefore Clojure can run on lots of different devices.
This could be a problem a few years ago because of the computing power
required to run a virtual machine. But today the benefit of being able to
build for a virtual machine instead of an OS greatly surpasses the drawbacks
that the JVM brings.
11
6
References
Clojures Homepage
http://clojure.org/
17-02-2014
Clojure programming guide
http://java.ociweb.com/mark/clojure/article.html
17-02-2014
12