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
© Copyright 2024 ExpyDoc