14 Languages - Clojure
2015-11-30
14 Languages - Clojure
The sixth language - Clojure
Introduction
Clojure is a LISP dialect with some extensions and some simplification. Like Scala and a small army of other languages, Clojure runs on the JVM with all the benefits that comes with doing so. It is a functional language that, as Erlang, contains some non-functional things.
Whenever i think of LISP, I remember the old joke that LISP stands for Lots of Irritating Stupid Parenthesis. There are lots of parenthesis in Clojure, but not so many as in regular LISP. As we will see, lists, sets and hashes uses other start and end markers. Commas are even allowed in some places. That's what I call progress ;)
Clojure is strongly and dynamically typed.
Executing Clojure programs
Clojure programs can be executed in an interpreter called repl. To start repl one can either download the Clojure distribution from clojure.org, unpack it and run java -cp clojure-1.7.0.jar clojure.main
or one can use leinigen. Leiningen is i project for packaging and running Clojure programs. After having installed it one can invoke lein repl
to start the interpreter.
Clojure programs can also be executed directly from the command line by storing it a text file (conventionally having the .clj extension) and invoking (here assuming that the installed version is 1.7.0) java -cp ~/opt/clojure/clojure-1.7.0/clojure-1.7.0.jar clojure.main myProgram.clj
Once the interpreter is started, what can be done with it?
Basic Syntax
In Clojure, like in all functional languages, mostly everything is about function. To invoke a function in Clojure, one encloses the name of the function and parameters in parenthesis. The name goes right after the opening parenthesis, then comes the params separated by spaces. An example:
user=> (functionOne paramOne paramTwo)
Listing 1: Invoking a function
Invoking an operator is done in the same way, using prefix notation:
user=> (/ 12 3)
...
4
Listing 2: Using the division operator.
In the example we invoke the division operator supplying 12 as the nominator and 3 the denominator.
It is also possible to supply more than two operands to an operator. The operator is applied to the operands from left to right.
user=> (/ 12 3 2)
2
...
Listing 3: Using more than two operands.
Operations can be nested as desired:
user=> (/ (+ 8 4) (* 1 3) (- 4 2))
...
2
Listing 4: Nesting operations.
Comments
Clojure uses semicolons for line comment and the construct (comment)
for block comments. An example:
user=> (+ 37 5) ; The answer to everything.
42
Listing 5: Line comment.
Block comments can be used like this:
user=> (comment This is a longer comment
#_=> split
#_=> over
#_=> several lines.)
...
nil
Listing 6: Block comment.
Hello World!
Text can be written to std out by invoking the println
function. Thus, the Hello World program can be written like this:
user=> (println "Hello World!")
...
Hello World!
nil
Listing 7: Hello World in Clojure
Strings
Other types can be converted to strings using the str
function. If the object being converted is an instance of a Java class, the underlying objects toString()
method will be invoked.
str
can take from one to many arguments and the result will be be concatenated string that consists of the string representations of all the parameters. Using str
is therefore an easy way to concatenate a number of strings and other types together. An example:
user=> (str "one " 1 " two " 2)
"one 1 two 2"
Listing 8: Using str to concatenate things together.
Variables
A variable can be assigned using the (def variableName value)
construct. E.g.
(def message "Hello!")
...
user=> (def message "Hello!")
#'user/message
Listing 9: Defining a variable
The value of the variable can be found out by just stating the name of the variable at the prompt:
user=> message
"Hello!"
Listing 10: Printing the value of a variable.
Boolean operators
Clojure has the regular comparison operators: <, > <=, >= and =
. The "not equals" operator looks like this: not=
.
Keywords
Just like symbols in Ruby and atoms in Erlang, Clojure has a concept of "an immutable thingy representing something the programmer wants it to represent". I Clojure such a thing is called a keyword and is constructed by prepending a colon to a name: :thisIsAKeyword
Lists
A list can be constructed in two different ways. The first is to use the list
function:
user=> (list "a" "b" "c")
("a" "b" "c")
Listing 11: Creating a list using the list function.
The other way to construct a list is by using quoting:
user=> '("d" "e" "f")
...
("d" "e" "f")
Listing 12: List constructed via quoting.
So why can't a list be constructed like this ("This" "does" "not" "work")
? That's because lists are evaluated as functions. The list in this example would be interpreted as a call to the method named "This" with params "does", "not" and "work". And that does not work.
The basic operations that are possible to perform on a list are these:
first
- extract the first element of the list.rest
- extract sublist consisting of everything but the first element.last
- extract the last element of the list.cons
- construct a new list by appending a head to a tail.
An example exercising the basic functions:
user=> (first '(1 2 3 4 5 6))
1
user=> (last '(1 2 3 4 5 6))
6
user=> (rest '(1 2 3 4 5 6))
(2 3 4 5 6)
user=> (cons 0 '(1 2 3 4 5 6))
(0 1 2 3 4 5 6)
Listing 13: Basic list functions.
Vectors
A vector is similar to a list. One difference between them is that vectors are optimized for random access, lists are not. Another difference is more conceptual: lists are meant to be used for code, vectors for data.
To create a vector the bracket syntax is used: ["a" "b" "c" "d"]
user=> ["a" "b" "c" "d"]
...
["a" "b" "c" "d"]
Listing 14: Creating a vector.
Some of the operations that can be performed on vectors are these:
first
- extract the first element of the vector.rest
- extract a vector consisting of everything but the first element.last
- extract the last element of the .nth
- return the n:th element of the vector.
user=> (["a" "b" "c"] 0)
"a"
user=> (first ["a" "b" "c"])
"a"
user=> (rest ["a" "b" "c"])
("b" "c")
user=> (last ["a" "b" "c"])
"c"
user=> (nth ["a" "b" "c"] 1)
"b"
Listing 15: Basic vector functions.
Vectors can also be used as functions themselves taking an index as a parameter. When a vector is used as a function it returns the element having the supplied index.
user=> (["a" "b" "c"] 0)
...
"a"
Listing 16: Vector used as a function.
Sets
A set is created similar to how a list or a vector is created but instead of using parenthesis or brackets, the elements are enclosed by #{}
.
user=> #{"a" "b" "c"}
#{"a" "b" "c"}
Listing 17: Creating a set.
As a set does not have any order, it is of course not possible to fetch individual elements of a set but several other operations exist on sets. The following lists some of them. In the examples the following set are used
(def aSet #{"H" "A" "L"})
count
- count the number of elements in a set :(count aSet) 3
sort
- creates a list in which the elements in the set are sorted:user=> (sort aSet) ("A" "H" "L")
sorted-set
- returns a sorted set containing the parameters given to it:user=> (sorted-set "H" "A" "L") #{"A" "H" "L"}
clojure.set/union
- returns a set that is the union between two sets.user=> (clojure.set/union #{1 2 3} #{3 4 5}) #{1 4 3 2 5}
clojure.set/difference
- returns a set that is the difference between two sets:user=> (clojure.set/difference #{1 2 3} #{3 4 5}) #{1 2}
To test if an element is contained in a set, a somewhat peculiar syntax can be used. One simply uses the set as a function a gives the value that should be tested for membership as a param. If is is included, the value itself will be returned. If not then nil
will be returned:
user=> (#{1 2 3} 2)
2
user=> (#{1 2 3} 42)
nil
Listing 18: Testing is a value is included in a set.
Maps
Maps can be created by enclosing a comma separated list of items in curly brackets. Each item should consist of a key and a value separated by a space.
{"key1" "value1", "key2" "value2"}
To retrieve the value for a specific key, just invoke the map as you would have a function and pass the key as argument:
user=> (def aMap {"key1" "value1", "key2" "value2"})
#'user/aMap
user=> (aMap "key1")
"value1"
Listing 19: Retrieving a value for a key from a map.
Interesting enough, if the keys in the map are keywords, a keyword can be used like a function to look up its corresponding value in a map:
user=> (def bMap {:keyA "valueA", :keyB "valueB"})
#'user/bMap
user=> (:keyB bMap)
"valueB"
Listing 20: Using keywords in a map.
So what operations can be performed on a map? Here are some:
-
merge
- merge two maps:user=> (merge {"one" 1, "two" 2} {"three" 3, "four" 4}) {"one" 1, "two" 2, "three" 3, "four" 4}
-
merge-with
- merge two maps specifying an operator to use when keys exist in both maps:user=> (merge-with + {"one" 1, "two" 2} {"two" 2, "three" 3}) {"one" 1, "two" 4, "three" 3}
-
assoc
- add a key-value pair to a map:user=> (assoc {"one" 1} "two" 2) {"one" 1, "two" 2}
-
sorted-map
- creates a map in which the keys are sorted from a number of key-value pairs:user=> (sorted-map 2 "two", 1 "one", 3 "three") {1 "one", 2 "two", 3 "three"}
Functions
The defn
keyword is used to defined a function:
(defn aFunction [anArgument] (str "The argument was: " anArgument))
user=> (aFunction "this is the argument")
"The argument was: this is the argument"
Documentation can be added to a function by supplying an extra string between the name of the function and the parameter list:
user=> (defn greet "Prints a nice greeting" [] (str "Hello dear sir/madam! What a lovely day!"))
. Info about a function can be retrieved by invoking the doc
function: user=> (doc greet)
-------------------------
user/greet
([])
Prints a nice greeting
nil
Destructuring
Destructuring is the concept of picking out parts of a parameter list containing list, vectors, maps and similar data structure. It's somewhat similar to pattern matching i Prolog. So how does destructuring look? One simply type a data structure like the one that the one the function expects in the parameter list, names the parts that should be extracted and puts under scores in the places where the parts that could be ignored are. For example, of a function expects a vector of which only the second element if of interest, the pattern [_ second _]
could be used inside the parameter list. Let's try it out:
user=> (defn aFunction [[_ second _]] (str "The second element of the vector was " second))
#'user/aFunction
user=> (aFunction [1 2 3 4])
"The second element of the vector was 2"
Listing 21: Destructuring a vector.
The data structure that is destructured could be nested in one or more levels. E.g. the second element of the second element in a vector containing other vectors could be extracted like this:
user=> (defn bFunction [[_ [_ secondSecond _] _]] (str "The second element of second element of the input the vector was " secondSecond))
#'user/bFunction
user=> (bFunction [[1 2 3] [4 5 6] [7 8 9]])
"The second element of second element of the input the vector was 5"
Listing 22: Destructuring a vector of vectors.
To shorten the code a bit, trailing under scores could be skipped. The function above could thus be constructed like this:
user=> (defn bFunction [[_ [_ secondSecond]]] (str "The second element of second element of the input the vector was " secondSecond))
#'user/bFunction
user=> (bFunction [[1 2 3] [4 5 6] [7 8 9]])
"The second element of second element of the input the vector was 5"
Listing 23: Destructuring a vector of vectors with trailing under scores removed.
In addition to parameter lists, destructuring can also be performed using a let
statement. It take a pattern and a value to apply that pattern to as first param. As second param it takes an expression or a statement that, presumably, uses the data that has been extracted during the destructuring. We could rewrite the previous example extracting the second element of the second vector like this:
user=> (defn middle [matrix]
#_=> (let [ [_ [_ mid _] _] matrix] (str "The second element of second element of the input the vector was " mid)))
#'user/middle
user=> (middle [[1 2 3] [4 5 6] [7 8 9]])
"The second element of second element of the input the vector was 5"
Listing 24: Destructuring a vector of vectors using let.
If-clauses (if, and, or, do, when)
If clauses in Clojure is similar to those in Io.
user=> (if true (println "It is true!"))
It is true!
nil
Listing 25: Simple if clause
To test several conditions and
and or
can be used.
user=> (if (and true true) (println "This is true"))
This is true
nil
user=> (if (and true false) (println "This is false"))
nil
user=> (if (or true false) (println "This is true"))
This is true
nil
Listing 26: and and or
Clauses including an else part can be constructed like this:
user=> (if (< 2 1) (println "Two is less than one") (println "Two is greater than one"))
...
Two is greater than one
nil
Listing 27: If clause containing an else part.
To be able to evaluate several expressions in one (or both) parts of an if expression, the do
expression can be used. It evaluates all expressions given as parameters to it and returns the value of the last. An example:
user=> (if true (do (println "This is printed") (println "This is also printed")))
This is printed
This is also printed
nil
Listing 28: If clause containing a do expression.
This can also be accomplished using the when
expression. It is like an if
with an implicit do
:
user=> (when true (println "This is printed") (println "This is also printed"))
This is printed
This is also printed
nil
Listing 29: when expression.
map
Like in many (most?) other functional languages a function can be applied to all elements in a list/vector by using map
. An example of doing just that can look like this:
ser=>(def aList '("a" "bb" "ccc" "dddd"))
#'user/aList
user=> (map count aList)
(1 2 3 4)
Listing 30: Using map to count the number of letters of each item in a list.
Of course it is not only built in functions like count
that can be applied to a list with map
. User defined functions works equally well. If we for example define a function that adds one to the integer it receives as input, we can apply that to a list of integers like this:
user=> (defn plusOne [i] i + 1)
#'user/plusOne
user=> (def bList '(1 2 3 4 5))
#'user/bList
user=> (map plusOne bList)
(2 3 4 5 6)
Listing 31: Applying a custom function to a list of integers using map.
filter
filter
can be used to, well, filter elements in a list. It takes a condition and a list, and returns a new list containing only the elements of the new list that fulfills the condition. An example:
user=> (def bList '(1 2 3 4))
#'user/bList
user=> (defn lessThanThree [i] (< i 3))
#'user/lessThanThree
user=> (filter lessThanThree bList)
(1 2)
Listing 32: Using filter to extract elements from a list.
apply
To apply a function to a list as a whole apply
can be used. E.g. to sum the elements in a list, the following code can be used:
user=> (apply + bList)
10
Listing 33: Using apply to sum the elements in a list.
Anonymous Functions
Creating a named function just to be able to use it in a map
call can be a little burdensome. Writing an anonymous function is a way to create a function with a little less overhead. It is meant to be used when the function does not need to have a name. For example, when it just should be used as a parameter to a map
call.
An anonymous function is creating using the (fn [params] (body))
syntax. If we rewrite our earlier example of using map
to increase the integers in a list by 1 using an anonymous function, we get this:
user=> (map (fn [i] (+ i 1)) bList)
(2 3 4 5)
Listing 34: Using an anonymous function to increase the integers in a list by one.
There is also a shorter form of an anonymous function called a reader macro. When using that, the fn
is replaced by a #, no argument list is used but the single parameter that is supplied can be accessed by the % symbol. The previous example could thus be rewritten like this:
user=> (map #(+ % 1) bList)
(2 3 4 5)
Listing 35: Using a reader macro to increase the integers in a list by one.
reduce
To condense the elements in a list to a single value reduce
can be used. E.g. to sum the elements in a list of integers, you could do like this:
user=> (def aList '(1 2 3 4 5))
#'user/aList
user=> (reduce + aList)
15
sort
A list can be sorted using sort
:
user=> (def bList '(1 2 99 4 5 -1 73 58))
#'user/bList
user=> (sort bList)
(-1 1 2 4 5 58 73 99)
A function can be used to supply the sort order. In this case sort-by
is used instead of sort
.
user=> (defn abs [i] (if (< i 0) (* i -1) i))
#'user/abs
user=> (def cList '(0 -2 103 -4 5 -1 -73 58))
#'user/cList
user=> (sort-by abs cList)
(0 -1 -2 -4 5 58 -73 103)
Listing 36: Using a function to supplying the sort order.
Recursion
As in other functional languages, recursion is used heavily in Clojure. A function computing the factorial of a number can for example be implemented like this:
(defn factorial [n]
(if (= n 0)
1
(* n (factorial (- n 1)))
)
)
Listing 37: Recursive factorial program.
Loops
Quite surprisingly considering it is a functional language, Clojure supports an explicit iteration construct using the loop
and recur
keywords. The loop
sets up the variables used in the loop. recur
calls the corresponding loop
statement passing on new values for the parameters to be used in the next iteration. To avoid looping indefinitely the body of the loop must of course contain an if
clause in which the recur
statement is put.
A function using loop-recur
to calculate the factorial could look like this:
(defn factorialUsingLoop [n]
(loop [nn n, product 1]
(if (<= nn 1)
product
(recur (- nn 1) (* product nn))
)
)
)
Listing 38: Factorial program using loop-recur.
So how does the program above work? It takes one param which, obviously, is the number to calculate the factorial of. In the loop
construct, we set up two variables nn
and product
. nn
is the counter which we will use to keep track of which iteration we are currently on and product
is an accumulator in which we store the current result. The variables are initially assigned the values n and 1 respectively.
Next comes the guard statement: if we've reached 1 we're finished and can return the current product as the final result. Otherwise the use recur
to jump back to the loop
statement. This time however, we pass new values to loop
by supplying parameters to recur
. The counter is decreased by one to indicate that we've completed an iteration and we multiply the accumulator by the current value of nn. The latter is of course how we calculate the factorial. In the next iteration of the loop these values will be assigned to nn
and product
respectively.
Interaction with Java
The class of a variable can be determined by using the class
function:
user=> (class 1)
...
java.lang.Long
Listing 39: Using class
Static Java methods can be called using the ClassName/methodName
syntax:
user=> (System/currentTimeMillis)
1448913149737
Listing 40: Calling a static Java method without params.
Params are passed into the method as usual:
user=> (System/getProperty "java.vm.version")
"25.45-b02"
Listing 41: Calling a static Java method and passing params to it.
Static fields are accessed in the same way minus the parenthesis:
user=> Integer/MAX_VALUE
2147483647
Listing 42: Accessing a static field.
Instance methods use almost the same syntax but a period is added before the name of the method:
user=> (.toLowerCase "tHE cAPS lOCK kEY mUST dIE!")
"the caps lock key must die!"
Listing 43: Accessing an instance method
Instance fields are accessed by prepending .-
to the instance:
user=> (.-x (new java.awt.Point 10 200))
10
Listing 44: Accessing an instance field
In the last example above we also saw how to create a new Java object, using the new
function. As an alternative syntax, we can skip new
and add a period right after the name of the class:
user=> (.-x (java.awt.Point. 10 200))
10
Listing 45: Creating an instance of a class using the dot notation instead of using new.
Features
Sequences
All containers in Clojure are members of a concept that's called a sequence. There are a lot of functions in Clojure that operates on sequences. Let's explore some of them.
First out are some predicate functions. These are functions that take a test function as param and returns a boolean value. The return value is calculated by applying the test function to a number of the elements of the sequence. How many elements that need to be considered depends on the predicate function. The every?
function needs to process all elements, the not-every?
function can stop as soon as a non-compliant element is found.
Here's some of the predicate functions that exist in Clojure
every?
- does every element in the secuquence make the test function return true.not-every?
- is there at least one element in the secuquence that makes the test function return false.some
- is there at least one element in the secuquence that makes the test function return true.not-any?
- is there no element in the secuquence that makes the test function return true
Note that some
does not end with a question mark!
As an example, let's write a test function that checks if a number is equal to 42 and use that with the predicate functions.
(defn isFortytwo? [i] (= i 42))
Listing 46: The extremely important function that checks if a number is equal to 42.
We can then use that function together with the predicate functions above by applying them to a test sequence (a list in this case):
user=> (def aSequence '(1 2 3 42 4711))
#'user/aSequence
user=> (every? isFortytwo? aSequence)
false
user=> (not-every? isFortytwo? aSequence)
true
user=> (some isFortytwo? aSequence)
true
user=> (not-any? isFortytwo? aSequence)
false
Listing 47: Using predicate functions to check for the occurrence of 42 in a list.
List comprehensions
Just like Erlang (and Python), Clojure supports list comprehensions. The syntax is like this: (for [aVariable aList, bVariable, ...] (doSomethingWithTheVariables))
. For example, if you want to loop through a list of chocolate bars and a list of sizes, you can do that with a single list comprehension like this:
user=> (def items '("Twix bar" "Snickers bar" "Milka bar"))
#'user/items
user=> (def sizes '("small" "medium" "large"))
#'user/sizes
user=> (for [item items, size sizes] (str "Item " item " of size " size))
("Item Twix bar of size small" "Item Twix bar of size medium" "Item Twix bar of size large" "Item Snickers bar of size small" "Item Snickers bar of size medium" "Item Snickers bar of size large" "Item Milka bar of size small" "Item Milka bar of size medium" "Item Milka bar of size large")
Listing 48: Using a list comprehension to generate all permutations of mixing two lists.
A filter can be applied to the list comprehension by applying the :when
keyword to the list comprehension
user=> (defn notSmall [size] (not= size "small"))
#'user/notSmall
user=> (for [item items, size sizes :when (notSmall size)] (str "Item " item " of size " size))
("Item Twix bar of size medium" "Item Twix bar of size large" "Item Snickers bar of size medium" "Item Snickers bar of size large" "Item Milka bar of size medium" "Item Milka bar of size large")
Listing 49: List comprehension including a filter.
Bounded sequence
A bounded sequence
can be created with the range
key word. For example to create a list of the numbers from one to ten you can do like this: user=> (range 1 10)
(1 2 3 4 5 6 7 8 9)
Listing 50: Using a range to create a bounded sequence.
A step could be added to the generation by supplying an extra parameter to range
. To generate all odd numbers in the interval from one to ten, this code could be used:
user=> (range 1 10 2)
(1 3 5 7 9)
Listing 51: Adding a step to range
.
Indefinite sequences
Clojure has a lot of ways to generate indefinite sequences. It uses lazy evaluation to just pick the data that is needed at the moment.
To avoid the problem of printing an indefinite sequence, if that would be possible, we will in the examples below use the take
function that extracts the first n elements from a sequence and skips the others.
So what ways are there then to generate an indefinite sequence? Here are some:
-
repeat
- just repeats the value passed to it as an argument:user=> (take 3 (repeat "hej")) ("hej" "hej" "hej")
-
cycle
- cycles through the elements of a vector:user=> (take 5 (cycle ["the" "lazy" "fox"])) ("the" "lazy" "fox" "the" "lazy")
-
iterate
- repeatedly applies a function to a value. The initial value is passed to the first iteration. Then result of each iteration is passes on to the next iteration :#'user/timesThree user=> (take 5 (iterate timesThree 1)) (1 3 9 27 81)
If you want you can pick out an element at a specific index from a sequence by using nth
:
user=> (nth (iterate timesThree 1) 5)
243
Listing 52: Picking out an element at a specific index.
Elements can be dropped from an indefinite sequence by applying drop
:
user=> (take 5 (drop 3 (iterate timesThree 1)))
(27 81 243 729 2187)
Listing 53: Dropping elements from an indefinite sequence.
If we want to place some fixed element between the elements in the indefinite sequence, this can be accomplished by using interpose
:
user=> (take 5 (interpose 42 (iterate timesThree 1)))
(1 42 3 42 9)
Listing 54: Placing a fixed element between each element of an indefinite sequence.
Two indefinite sequence can be interleaved by using, wait for it, interleave
:
user=> (take 9 (interleave (iterate timesThree 1) (cycle '("the" "lazy" "fox"))))
(1 "the" 3 "lazy" 9 "fox" 27 "the" 81)
Listing 55: Interleaving two indefinite sequences.
If we would like to change the syntax and have the sequence to the left and the operators that operate on the sequence to the right, we can do that by using the ->>
operator. I.e we start with the ->>
operator, then the generator for the sequence and finally we add one or more operators to the right. For example if we would like to extract the fourth to eighth of the sequence 1, 3, 9, 27, ..., we could do like this:
user=> (->> (iterate timesThree 1) (drop 3) (take 5))
(27 81 243 729 2187)
Listing 56: Using ->> to reverse the order in which operations of indefinite sequences are written.
macros
Macros is a way to extend the Clojure language itself. So why can't we do this by just creating functions and invoking them? The basic problem is that all parameters in a function call always get evaluated at the time the function is called. That might not be what we desire. If, for example, we want to create a function printMessageIfFirstParamIs42
that takes a number and a code block as parameter. The code block should only be executed if the number is 42. This is impossible to accomplish using a function because the code block will be executed when the call to the function is made in the process of expanding the parameters.
user=> (defn printMessageIfFirstParamIs42 [i codeBlock] (if (= i 42) codeBlock))
#'user/printMessageIfFirstParamIs42
user=> (printMessageIfFirstParamIs42 4711 (println "It was 42!"))
It was 42!
nil
Listing 57: Non-working function.
The problem here, as stated before, was that the code block (println "It was 42!")
was evaluated before printMessageIfFirstParamIs42
.
So how do we solve this? Macros to the rescue! ;)
A macro let you defer the execution of a piece of code until "later". First the macro is expanded, then the expanded code is executed.
A macro is defined using defmacro
and all code that should be included in a macro should be packed in lists. Also all keywords (if
, not
, ...) needs to be prepended by a quote to avoid confusing the interpreter.
We can rewrite our printMessageIfFirstParamIs42 function like as a macro:
user=> (defmacro printMessageIfFirstParamIs42 [i codeBlock] (list 'if (= i 42) codeBlock) )
#'user/printMessageIfFirstParamIs42
user=> (printMessageIfFirstParamIs42 4711 (println "It was 42!"))
nil
user=> (printMessageIfFirstParamIs42 42 (println "It was 42!"))
It was 42!
nil
Listing 58: A working version of printMessageIfFirstParamIs42 written as a macro.
Software Transactional Memory
Clojore has constructs to support Software Transactional Memory (STM). So what does that mean? It means that we can create variables whose values only can be altered from a transaction. What benefit has that then? If one want to share state between two or more concurrently executing threads, STM helps making sure that only one thread at a time alters the value of the variable.
One language construct that supports STM in Clojure is the reference or ref for short. A ref is created using the ref
. For example, to create a ref that should hold the current language under examination, you can do like this: user=> (ref "Clojure")
#object[clojure.lang.Ref 0x1e11eb69 {:status :ready, :val "Clojure"}]
Listing 59: Creating a reference.
Note that we've only created a reference, we've not assigned a value to it (so it is pretty useless ;). To do that we create a regular variable and associate it with the ref
user=> (def languageUnderInvestigation (ref "Clojure"))
#'user/languageUnderInvestigation
user=> @languageUnderInvestigation
"Clojure"
Listing 60: Creating a reference and assigning it to a variable.
To retrieve the value of a reference you use deref
or its shorter form @
:
user=> (deref languageUnderInvestigation)
"Clojure"
user=> @languageUnderInvestigation
"Clojure"
user=>
Listing 61: Retrieving the value of a reference in two ways.
To assign a new value to the reference, the ref-set
method can be used. If one tries to do this without being in a transaction, the following happens:
user=> (ref-set languageUnderInvestigation "Haskell")
IllegalStateException No transaction running clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)
Listing 62: Trying to change the value of a reference while not being in a transaction.
A transaction can be started by using dosync
. Wrapped in a dosync
, the changing of the value succeeds:
user=> (dosync (ref-set languageUnderInvestigation "Haskell"))
"Haskell"
user=> @languageUnderInvestigation
"Haskell"
Listing 63: Altering the value of a reference from within a transaction.
Agents
Agent are entities that have state and where that state could be modified asynchronously where the agent handles the threading and locking automatically by itself and guarantees that two threads don't alter the state concurrently.
An agent is created using the agent
keyword. As with references, it it is a good idea to assign the agent to variable when creating it so that it is easy to access later on. When creating an agent the inital state could be provided:
user=> (def doubleOhSeven (agent 7))
#'user/doubleOhSeven
Listing 64:Creating an agent.
The state of the agent can then be altered by send
:ing functions to it to execute. Let's create a function that computes the square of in integer
user=> (defn square [i] (* i i))
#'user/square
Listing 65: Method that calculates the square of an integer.
Now we can tell the agent to changes its value by executing square
:
user=> (send doubleOhSeven square)
#object[clojure.lang.Agent 0x314af27 {:status :ready, :val 49}]
Listing 66: Altering the state of an agent by sending it a function to exeute.
The value of an agent can, just like the value of a reference, be retrieved by using @
:
user=> @doubleOhSeven
49
Listing 67: Retrieving the value of an agent.
Reading from an agent, or a reference, will never block. It always returns immediately. It is therefore not possible to determine if an update of the contained value is in progress.
Futures
Futurres are something that exist in other languages as well. The Clojure version of a future defines a set of operations that are executed in another thread. The future is created asynchronously and the call to create it returns immediately. The dereferencing operation, however, is synchronous and might hang if the operations that were specified at creation time is not finished. When they have completed, the dereferencing call returns with the result, which is the value of the last operation the future executed. An example:
user=> (def calculateMeaningOfLife (future (Thread/sleep 7000) 42))
#'user/calculateMeaningOfLife
user=> @calculateMeaningOfLife
42
Listing 68: Calculating the meaning of life using a future.
In the code above, we use the sleep()
method in java.lang.Thread
to simulate that it takes a little time to calculate the value to return.
Gotchas
- The names of most predicate functions end with a question mark but
some
does not. - The not equals operator is
not=
.
FizzBuzz in Clojure
(defn fizzBuzz []
(loop [counter 1]
(if (<= counter 100)
(do
(if (= (rem counter 3) 0) (print "Fizz"))
(if (= (rem counter 5) 0) (print "Buzz"))
(if (and (not= (rem counter 3) 0) (not= (rem counter 5) 0)) (print counter))
(println "")
(recur (+ counter 1))
)
)
)
)
What I like about Clojure
- The syntax! Reverse polish notation is fun!
- The support for indefinite sequences. It really fun to play around with
iterate
,take
,drop
and so on!
What I don't like about Clojure
- Sometimes there still are a confusing number of parenthesis in a clojure program...
- The
loop - recur
construct is not very...elegant.
Onwards, upwards
I think Clojure is the language, so far, that I've liked the most. It almost feels like an adventure game to try to pussle out the correct order of function calls in order to accomplish what I want to accomplish. I like Clojure so much that I will order a book that only focuses on Clojure right after I've completed this blog entry. I suppose a blog review of that will appear on the blog sometime in Q1 next year. Hopefully. I still have one language left to finish this year. Haskell!
Previous parts in the series
1. Ruby2. Io
3. Prolog
4. Scala
5. Erlang
Links
[1] Clojure homepage[2] Leiningen homepage
Leave a reply