Mastering Clojure Fundamentals for Beginners
1. Getting Started1.1. Running the Clojure InterpreterClojure is powerful, easy to use, general purpose programming language that runs on the JVM. Clojure like other Lisp dialects is dynamic, which means that it is not overly typed based system and gives you some room to breathe. In addition to that Clojure is compiled and can easily call upon the vast amount of Java libraries and the goodness of the JVM. I have created this series keeping in mind the average programmer who is more experienced in C-based programming languages like Java, Python, JavaScript. If you come from such programming languages and are looking into exploring the world of a full-fledged functional programming language, then Clojure is the one for you.This chapter is the starting of a series on Clojure. We will try to see the internals of Clojure and at the end of it seek to build a real world app.You can run Clojure both as a jar file or as an interpreter. We will look at how to distribute the Clojure code as a jar file later on. Right now lets first start Clojure as an interpreter. To do that install leiningen which is considered to be the easiest way to use Clojure.First, you need to install Java if you don’t have that installed already. The below instructions are for an Ubuntu machine. In case you have a different machine and are not able to install the dependencies, write to me and we will try to find what the issue might be. sudo apt-get update sudo add-apt-repository ppa:webupd8team/java sudo apt-get update sudo apt install oracle-java8-installerview rawinstall_java8_ubuntu.bash hosted with ❤ by GitHubYou can then follow the installation steps as shown below. This is the same as the official installation steps which I followed in my Ubuntu machine. ➜ ~ wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein --2017-06-14 19:57:44-- https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.8.133 Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.8.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 12871 (13K) [text/plain] Saving to: ‘lein’ lein 100%[==========================================================>] 12.57K --.-KB/s in 0.007s 2017-06-14 19:57:45 (1.72 MB/s) - ‘lein’ saved [12871/12871] ➜ ~ sudo mv lein /usr/local/bin/lein [sudo] password for joy: ➜ ~ sudo chmod a+x /usr/local/bin/leinview rawinstall_leiningen.bash hosted with ❤ by GitHubOnce done you can start the lein repl command and this will download the necessary libraries and start a repl for you. ➜ ~ lein repl nREPL server started on port 37057 on host 127.0.0.1 - nrepl://127.0.0.1:37057 REPL-y 0.3.7, nREPL 0.2.12 Clojure 1.8.0 Java HotSpot(TM) 64-Bit Server VM 1.8.0_131-b11 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e user=>view rawlein_repl.bash hosted with ❤ by GitHubNow check if you can run the code and if it gives the correct result.user=> (+ 1 1)2Voila! You have successfully started your Clojure interpreter and have run a Clojure program.1.2 AssignmentsThe use of global variables is recommended to avoid in Clojure and to keep the functions as clean and pure as possible. But you can create a persistent reference to a changing object. This is done through the use of Vars. user=> (def x 1) #'user/x user=> x 1 user=> (def y 2) #'user/y user=> (+ x y) 3 user=>view rawasignment.clj hosted with ❤ by GitHubVars are generally static. So, if you need to make it dynamic and have some calculations based on them, you will need to use the macro binding. Bindings are thread-local and do not propagate outside the thread. user=> (def ^:dynamic x 1) #'user/x user=> (def ^:dynamic y 1) #'user/y user=> (binding [x 2 y 3] (+ x y)) 5 user=> (+ x y) 2 user=>view rawdunamic_vars.clj hosted with ❤ by GitHub1.3 NumbersClojure is a dialect of Lisp and that means that means that operators are considered as functions and so come before the arguments that should go into the operation. user=> (+ 1 1) 2 user=> (* 2 3) 6 user=> (/ 17 5) 17/5 user=> ( float (/ 17 5)) 3.4view rawnumbers.clj hosted with ❤ by GitHub1.4 StringsStrings are believed to be one of the basic building blocks of programming. Almost everything that you do will involve manipulation of strings.In Clojure, a string literal is created by enclosing the string in double quotes.To concatenate some string variables you can use the `str` function: user=> (def a1 "My favorite lang is ") #'user/a1 user=> (def a2 "Clojure") #'user/a2 user=> (str a1 a2) "My favorite lang is Clojure" user=>view rawstrings.clj hosted with ❤ by GitHubYou can also use string formatting using the Format function. user=> (format "Any man whose errors take %s years to correct is quite the man." "10") "Any man whose errors take 10 years to correct is quite the man." user=>view rawstring_format.clj hosted with ❤ by GitHub1.5 FunctionsClojure is a functional programming language. So you will be creating a lot of functions. Functions that do something, functions that pass functions, functions that manipulate other functions and so on. You can consider a function to be unrealized functionality. Generally, functions are defined in the following manner.(defn name doc-string? attr-map? [params*] body)For example, a function that returns a square of a number can be created. (defn my_square "This is my my_square function" [x] (* x x)) (my_square 2)view rawsquare.clj hosted with ❤ by GitHubThe above code should return 4. Now the functions can be used in other expressions.(+ (my_square 2) (my_square 3))which should give the output 13.If you just need to create a small function that you can consider writing anonymous functions usingfn .user=> ((fn [x] (* x x)) 2)41.6 ConditionsOne way of implementing branching in Clojure is through the if statement which is basically a test. The format is basically:(if condition-statment <(optional)if condition is false return this>)The above construct will be clear in the below examples.user=> (if nil “truth value” “false value”)“false value”In Clojure, only nil and false are considered logical false.user=> (if “true” “truth value” “false value”)“truth value”But if cannot take multiple tests in the format of if..elif..else. For that you will need to use thecond macro. Then you can pass multiple test parameters. user=> (defn odd-even #_=> "Determines whether or not n is odd or even." #_=> [n] #_=> (cond #_=> (= (rem n 2) 0) "even" #_=> (= (rem n 2) 1) "odd")) #'user/odd-even user=> user=> (odd-even 2) "even" user=> (odd-even 3) "odd"view rawmultiple_conditions.clj hosted with ❤ by GitHubThe above constructs are more in line with the imperative languages.Of course, you can convert any if constructs to filter, but more on that later.1.7 Data Containers and Data Structures.Clojure has four primary data structure or collections of data.1.Lists2.Vectors3.Maps4.SetsLists in Clojure are singly linked lists. So modifications of lists are efficient while random access is not. Lists are one of the most important data structures in Clojure. A code is data in Clojure and they are represented in lists.You can either use the list function or the quote literal to creating a list.user=> (list “clojure” “F#” “python” “java”)(“clojure” “F#” “python” “java”)user=> ‘(“clojure” “F#” “python” “java”)(“clojure” “F#” “python” “java”)Get the first or nth element of the list. user=> (def langs (list "clojure" "F#" "python" "java")) #'user/langs user=> (first langs) "clojure" user=> (nth langs 2) "python"view rawlist_lookup.clj hosted with ❤ by GitHubYou can treat the list like a stack with peek and pop. user=> (peek langs) "clojure" user=> langs ("clojure" "F#" "python" "java") user=> (pop langs) ("F#" "python" "java") user=>view rawas_stack.clj hosted with ❤ by GitHubYou can also extend a list to a new list with conj and concat. Notice that the conjoining in lists happen at the beginning of the list. user=> (conj langs "APL") ("APL" "clojure" "F#" "python" "java") user=> (conj langs '("OCAML" "erlang")) (("OCAML" "erlang") "clojure" "F#" "python" "java") user=> (concat langs '("OCAML" "erlang")) ("clojure" "F#" "python" "java" "OCAML" "erlang")view rawlist_manipulation.clj hosted with ❤ by GitHubB. VectorsVectors are similar to arrays in C and Java. The elements in a vector are indexed by contiguous integers. Vectors are optimized for random lookups.To create a vector you can use the vector function or just define them. user=> (vector "Amherstia Nobilis" "Adansonia digitata" "Ficus bengalensis var - krishnae" "Araucaria Cookie" "Bombax Ceiba") ["Amherstia Nobilis" "Adansonia digitata" "Ficus bengalensis var - krishnae" "Araucaria Cookie" "Bombax Ceiba"] user=> ["Amherstia Nobilis" "Adansonia digitata" "Ficus bengalensis var - krishnae" "Araucaria Cookie" "Bombax Ceiba"] ["Amherstia Nobilis" "Adansonia digitata" "Ficus bengalensis var - krishnae" "Araucaria Cookie" "Bombax Ceiba"]view rawvector_defn.clj hosted with ❤ by GitHubRetrieving the nth element of the vector is optimised for a vector. user=> (def plants-in-lalbagh ["Amherstia Nobilis" "Adansonia digitata" "Ficus bengalensis var - krishnae" "Araucaria Cookie" "Bombax Ceiba"]) #'user/plants-in-lalbagh user=> user=> (nth plants-in-lalbagh 2) "Ficus bengalensis var - krishnae" user=> (get plants-in-lalbagh 2) "Ficus bengalensis var - krishnae" user=>view rawvector_lookup.clj hosted with ❤ by GitHubConcatenation works the same way to lists but conjoining happens at the end of the vector. user=> (def plants-in-lalbagh ["Amherstia Nobilis" "Adansonia digitata" "Ficus bengalensis var - krishnae" "Araucaria Cookie" "Bombax Ceiba"]) #'user/plants-in-lalbagh user=> user=> (nth plants-in-lalbagh 2) "Ficus bengalensis var - krishnae" user=> (get plants-in-lalbagh 2) "Ficus bengalensis var - krishnae" user=> (conj plants-in-lalbagh "eucalyptus") ["Amherstia Nobilis" "Adansonia digitata" "Ficus bengalensis var - krishnae" "Araucaria Cookie" "Bombax Ceiba" "eucalyptus"] user=> (def plants-in-lalbagh1 ["Amherstia Nobilis" "Adansonia digitata"]) #'user/plants-in-lalbagh1 user=> (def plants-in-lalbagh2 ["Araucaria Cookie" "Bombax Ceiba"]) #'user/plants-in-lalbagh2 user=> (concat plants-in-lalbagh) plants-in-lalbagh plants-in-lalbagh1 plants-in-lalbagh2 user=> (concat plants-in-lalbagh1 plants-in-lalbagh) plants-in-lalbagh plants-in-lalbagh1 plants-in-lalbagh2 user=> (concat plants-in-lalbagh1 plants-in-lalbagh2) ("Amherstia Nobilis" "Adansonia digitata" "Araucaria Cookie" "Bombax Ceiba") user=>view rawvector_characteristics.clj hosted with ❤ by GitHubC. Maps or HashMaps.Maps are equivalent to dictionaries in Python and HashMaps in Java. Maps associate keys to values and the keys must be comparable. There are numerous implementations of maps with various guarantees of ordering and different performance on the lookups.You can create a map using literals.user=> (def fav-filmmakers {:stanley-kubrik “26 July 1928”#_=> :satyajit-ray “2 May 1921”})#’user/fav-filmmakersuser=>Note that keywords are used as keys here. This is more of a convention and not really required. To access the value corresponding to a particular key, you can use the get function or use the key specified as a function. user=> ;; keys as a function. user=> (:satyajit-ray fav-filmmakers) "2 May 1921" user=> ;; using the get function. user=> (get fav-filmmakers :satyajit-ray) "2 May 1921"view rawmap_lookup.clj hosted with ❤ by GitHubD. SetsSets are similar to mathematical sets and are a collection of unique values. They are hashed and unordered.To create a set you can use a literal or the set function.user=> #{1 2 3 4 5}#{1 4 3 2 5}user=> (set [1 2 3 4 5])#{1 4 3 2 5}Sets are collections so you can count, conjugate them with other elements or break them or see if they contain some element. user=> (def banned-books #{"the hindus", "The God of Small Things"}) #'user/banned-books user=> (count banned-books) 2 user=> (seq banned-books) ("The God of Small Things" "the hindus") user=> (conj banned-books "Five Past Midnight in Bhopal") #{"The God of Small Things" "Five Past Midnight in Bhopal" "the hindus"} user=> (disj banned-books "The God of Small Things") #{"the hindus"} user=> (contains? banned-books "the hindus") trueview rawset_as_collections.clj hosted with ❤ by GitHubAs you can expect from the name, sets support set-operations like union, difference, intersection, etc. Set operations require the set namespace to be loaded. user=> (require '[clojure.set :as set]) niL user=> ; set operations user=> (set/union #{1 2} #{2 3} #{1 3 2} user=> (set/intersection #{1 2} #{2 3}) #{2} user=> (set/difference #{1 2} #{2 3}) #{1}view rawset_operations.clj hosted with ❤ by GitHubNote:DataStructures are functions:Maps are functions of their keysKeywords are functions of mapsSets are functions of their elementsVectors are functions of their indicesHope you liked this!To get regular updates on more content, learning material and general rants you can follow me on Twitter.
Learn More >