Created by Pavel KlavĂk
An interactive tutorial to Clojure. Code snippet are running Klipse and it is possible to change them. The results are immediately updated.
There is an entire book which explain benefits of representing data with maps and vectors, instead of objects from OOP. This reduces the complexity, makes the system more understandable and easier to change.
Keywords serve a different purpose, they identify themselves. They can be built programatically and are very fast. They are more similar to enums in languages like C++, but they are application wide, can be serialized, etc.
To avoid collisions, it is useful to distinguish different usages with namespace prefixes. This allows easy code refactoring and searching where each keyword is used. For example, in OrgPad, we have 15 different colors for nodes/links, each represented by a keyword :color/red, etc.
Clojure has well-known data types to represent numbers, strings, etc. When used, they evaluate to themselves.
Also written in ClojureScript.
https://github.com/viebel/klipse
https://blog.klipse.tech/clojure/2016/03/27/klipse-manual.html
I want to thank Yehonathan Sharvit (@viebel) for helping setting this up and improvements of several examples.
A map consists of key-value pairs where to each key has some value assigned. Lookup and adding/removing pair takes constant time.
Basic value can be combined into data structures. They have the following properties:
Here, you can take a look at overview of used technologies. The ClojureScript front-end runs in React with Reagent and Re-frame. The Clojure backend is written using Ring and Aleph.
An unordered collection of values, containing each value exactly once. Lookup and adding/removing values takes constant time.
An ordered collection of values. Lookup at an arbitrary index and adding/removing values from the end takes constant time.
Not everything can be immutable, otherwise the program could never change. Atoms can be used to capture state which changes over time.
All Clojure data structures and many other data types implement ISeq interface. A sequence is lazy, has the first element and the rest of the elements. Most Clojure functions work on sequences. See documentation for more information.
This property is called homoiconicity. Consider the following line:
(count [1 2 3 4 5])
As code, it is calling the count function on a vector [1 2 3 4 5]. But as data, it is a two element list containing the symbol count and the five element vector [1 2 3 4 5].
Most code transforms data from one shape into another. For example, each node of this website is described by a Clojure map. These maps are transformed into HTML data which is displayed on the screen.
Since code is just data, it is quite easy to write macros which take this data and transform it into another data, changing the code. Talking about macros is outside of scope of this tutorial, but for users I recommend this book:
In the thread-last macro ->, the input is inserted as the last argument. This is useful when a collection is transformed.
Linked lists for which it is efficient to add/remove values from the beginning. They are usually used to represent code and within macros.
Filter is given a function and a collection. Computes a sequence of those elements for which the function returns truthy value. Remove complements filter by keeping only the remaining elements.
Clojure is hosted on JVM and ClojureScript on JavaScript. It is use any library or use any native code. Guides for Clojure interop and ClojureScript interop. Click here to open the code in another tab.
Map takes a function and one or more collections. Computes a new sequence of results of applying the function on each element of the input collection(s).
Allows to build pipeline where some input data are processed by a series of transformations. These transformations are written from the first one till the last one.
Higher-order functions take other functions as arguments and apply them to collections. These transformations are nowadays in every programming language, and they are incredibly clean in Clojure.
Reduce is given a binary function, an initial value serving as an accumulator and a collection. It takes elements of the collection one-by-one. Applies the function for the current value of the accumulator and the processed element. The result is stored into the accumulator. The final value of the accumulator is returned.
In Clojure, a value is truthy when it is not nil and false. So 0, [], ... are truthy.
Instead of the usual f(x,y,z), we just write (f x y z). Commas are just whitespace in Clojure, so they are optional.
In the thread-first macro ->, the input is inserted as the first argument. This is useful when a single object is transformed: a map, a string, a single Java/JS object.
For number 1 to 100, output the following:
HTML is represented in Clojure using data structures in Hiccup format and it can be easily converted to HTML string using libraries. Reagent uses hiccup syntax to define React components. Hiccup is great since it is just a data structure which can easily be constructed, modified, transformed, etc.
Destructuring allows to quickly capture values from sequences and maps. It can be used in many places: at function arguments, in let statements, within for, doseq, etc.
Reagent is a ClojureScript wrapper for React using hiccup notation. Re-frame is a framework describing how to structure a Reagent app into four distinct parts: DB in a single atom, subscriptions which compute/read parts of DB, views which are rendered components, and events which modify DB and/or outside world. Both of these are awesome and OrgPad is build on this technology. See an interactive example.
A for cycle in languages such as C can have many different meaning. Distinct functions are used in Clojure, so the meaning is immediately clear.
for - individual values of a sequence are transformed independently, the computation could be paralelized.
for (int i = 0; i < 10; i++) { | (for [x arr] |
reduce - the values are cummulated, the cycle runs are not independent.
int sum = 0; | (reduce + arr) |
Let allows to define local bindings which are used in subsequent computations. It can save computation time when used repeatedly and make the code more readable.
Given a collection, for computes a sequence of results of applying the given form on each element. It is much more powerful than for in languages like C. Multiple collections can be used, it allows for filtering, local bindings, etc.