Programming in Clojure. Part 1: Why clojure
What Will I Learn?
In this part of the tutorial you will learn:
- Why and when Clojure can be a better tool for the task at hand, compared to other languages.
- How does Clojure differ from classical languages like Java.
- Setting up environment to start writing Clojure project.
- Using Leiningen for dependency management and building your project.
- Writing simple Clojure application.
Requirements
To follow this part of the tutorial it might be benefitial to have:
- Experience in other programming languages, especially ones designed for functional programming. Ideally Lisp dialects.
- Experience with functional programming
- Good mood and desire to learn
Difficulty
- Intermediate
Tutorial Contents
This tutorial will give you basic understanding of Clojure programming language, and describe what advantages it might have over other, more widely used languages.
Curriculum
- Part 1: Why Clojure?
- Part 2: Functional Programming
- Part 3: Basic data structures
- Part 4: Advanced data structures
- And maybe more...
Benefits of Clojure and it's history
Clojure is a dialect of Lisp family of programming languages. Lisp was originally created back in 1958, which is very long time ago in the world of computer science. The language was designed to be used in academic environment, but over the years it has evolved into numerous dialects, which are being used in many different industries, from classical eCommerce to aerospace engineering.
Clojure is running on Java Virtual Machine (JVM), which was developed by Sun Microsystems to run Java Programming language. Thanks to it's speed and popularity, advanced ecosystem and presense of simple, clear specifications, JVM became arguably the most popular target platform for developing new languages. Apart from Clojure, JVM hosts such languages as Scala, Kotlin, Jython (JVM implementation of Python), Groovy and, of course, Java.
Merging JVM's extensive toolkit with Lisp ideology allowed Clojure to become relatively famous in a very short time. Unlike most other Lisp dialects, Clojure provides features like interoperability with existing libraries (written in any JVM-supported language), ability to work with mutabile data structures, and support for advanced concurrency/parllelism techniques (ranging from primitive JVM-based constructs like locks and semaphors, to mechanics that are unique to Clojure and follow it's philosophy).
Lisp-like syntax might be confusing at the beginning, especially for people who are used to traditional C-like syntax, but it actually has many advantages which we will mention in future parts of this tutorial, during description of Clojure's Abstract Syntax Tree representation and macros.
Creating Clojure project
Since Clojure ecosystem is in many way compatible with Java's ecosystem, it is actually possible to use tools like Maven or Gradle for dependency and build management. Yet, for projects that are written exclusively in Clojure, it is much more convenient to use a tool which was designed with Clojure in mind. This tool in named Leiningen. Follow the instructions on the Leiningen home page to install it on your system.
To start a new Clojure project run:
$ lein new app laxam-clojure-tutorial-part1
This will create everything you need to get started. The directory structure will look like this:
$ tree laxam-clojure-tutorial-part1
.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── doc
│ └── intro.md
├── project.clj
├── resources
├── src
│ └── laxam_clojure_tutorial_part1
│ └── core.clj
└── test
└── laxam_clojure_tutorial_part1
└── core_test.clj
As you can see, Leiningen created not only sample source code files, but also documentation directory, README, licence and changelog files. It is a good idea to use them and keep them up to date on for any practical project.
File projet.clj
contains configuration for our project. Interestingly, project configuration is written in Clojure language. This allows developers to have flexibility to make configuration as dynamic as needed, which is not easy to do in languages that use static configuration files. First thing to do when creating new project is updating project description (and possibly licence name and url). One peculiar thing to note in project.clj
, is that clojure
is listed as a dependency. Unlike with most languages, Clojure compiler is actually a Java package, so we need to include it in every Clojure project. This stems from the fact that Clojure is working on top of JVM.
By inspecting ./src/laxam_clojure_tutorial_part1/core.clj
file, you can see that Leiningen has generated simple Hello World application for us. It contains just few lines of code:
(ns laxam-clojure-tutorial-part1.core
(:gen-class))
ns defines a namespace. For now you can think of namespaces as having function similar to package
directive in Java, although it's behaviour is actually quite different. We will discuss the topic of namespaces in more details in upcoming parts of this tutorial. For now just keep in mind that namespaces provide a way to structure you Clojure code in independent chunks to hide the internal complexity from the user. :gen-class
just tells compiler to generate .class
file for this namespace.
(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))
This block defines -main
function with defn keyword. -main
is a special name, because that's a function which will be called first when the app is executed. "I don't do a whole lot ... yet."
is a docstring (the concept might be familiar to you if you have experience with Python or Go). Docstrings are meant to be used as human-readable descriptions of a functions, which can be used to get information about the function during introspection in REPL or to generate documentation.
[& args]
denotes function with variable number of arguments. The last block is a body of -main
function. Function call in Clojure looks like (<function name> <argument 1> <argument 2> ... <argument n>), so
(println "Hello, World!")` calls built-in function println with one argument, which is a "Hello World" string.
Leiningen also created an example test file. You can inspect it in ./test/laxam_clojure_t\ utorial_part1/core_test.clj
:
(ns laxam-clojure-tutorial-part1.core-test
(:require [clojure.test :refer :all]
[laxam-clojure-tutorial-part1.core :refer :all]))
(deftest a-test
(testing "FIXME, I fail."
(is (= 0 1))))
Similarly to the core.clj
file, ns
here defines a namespace. In addition to that it also imports module clojure.test
, which is a built-in testing framework, and the main module laxam-clojure-tutorial-part1.core
, which will be tested.
The test itself uses this keywords:
deftest
to define a group of test casestesting
used to split the group into smaller sub-groupsis
is a generic assertion macro. Test fails if it's argument infalse
.
Sample project is actually a working, albeit trivial application. To execute it you need to simply run lein run
in root folder of the project. Leiningen will download all the dependencies for you (including Clojure), build the jar package and execute it. Similarly we can run sample test with lein test
.
Writing the first application
To get a feel of the language, let write a simple game. Player's task will be to guess the number between 1 and 100 chosen by the game at random. User can make a guess by entering the number in a command line. If number is incorrect, the game prints either "Your guess is too small" or "Your guess is too large". If number is correct, player wins and game prints number of guesses player made to win the game. While this game is certainly quite simple, it will serve our purpose to demonstrate the usage of many simple Clojure expressions.
First of all we will modify -main
function to do the preparation work for us. We will need to print a greeting to the user and invite him to play a game. We will also need to choose the random number between 1 and 100 and call another function to start a game. Let's call that function play
. It will take two arguments: number chosen and number of guesses taken by user.
(defn -main
[& args]
(println "I have a secret number between 0 and 100.")
(play (+ 1 (rand-int 100)) 0))
println
line is strightforward. (+ 1 (rand-int 100))
is a bit trickier, so let's explain it line by line:
- randint returns random number between 0 (inclusively) and it's first argument (exclusively).
- We need to have a number between 1 (inclusively) and 100 (inclusively), so we just add 1 to whatever
rand-int
returns with(+ 1 (rand-int 100))
. - We use resulting number as a first argument to
play
function (secret number). Second argument is zero, since user have not made any guesses just yet.
Before we go ahead implementing play
, we need another helper function, which will prompt user to enter a number, read it and return corresponding integer:
(defn ask-guess
"Ask player to make a guess"
[]
(print "Make your guess: ")
(flush)
(read-string (read-line)))
Again, let's analyze this simple function line-by-line:
print
is a function similar toprintln
, but it does not append newline character to the output.(flush)
is needed, because without it the string will be stored in internal buffer.read-line
takes an output from the user.read-string
takes a string and evaluates it as a Clojure code. If user enters string "10" it will return integer 10, which is exactly what we need. This function is good enough for our simple game, but bear in mind that it should be generally avoided in production environment, unless you are absolutely sure you know what you are doing. By allowing custom code executing it often poses serious security risk.
Now we are finally equipped to write the play
function:
(defn play
"Perform single step of the game"
[secret tries]
(let [guess (ask-guess)
wrong (fn [msg] (println msg) (play secret (+ 1 tries)))]
(cond
(> guess secret) (wrong "Your guess is too large")
(< guess secret) (wrong "Your guess is too small")
(= guess secret) (println "Congratulations! You won in" tries "tries!")
:else (do (println "I don't understand you")
(play secret tries)))))
As before, let's define the concepts used in this example:
(let [<name1> <value1> <name2> <value2> ... <name n> <value n>] (<body>))
allows you to bind value to a name. In other languages we talk about variables, but in Closure context this term might be misleading, because bindings are immutable. After we bound guess to the value returned by(ask-guess)
function, it normally cannot be changed (as mentioned before, Clojure can work with mutable variables as well, but that is much more advanced topic).- We use
let
to bind anonymous function(fn [msg] (println msg) (play secret (+ 1 tries)))
, which we are going to use later in the code. Anonymous function is similar to the function defined withdefn
, but it does not have a name. It does have access to all variable bindings in current scope. This function prints a message passed in a first argument and callsplay
recursively, with same secret and number of tries increased by one. cond
is similar to multipleif-else
statements in other languages. It checks the condition, and if it's true executes corresponding code. If guess is smaller or larger than secret, we callwrong
with corresponding message. If guess is exactly equal to secret, the game is over and we print the cogratulation message together with number of tires.- :else in
cond
denotes code to execute when all other checks failed. In our case this means that what user has entered was not recognized as a number, so we print this error message and give the player opportunity to guess again by callingplay
with the same arguments as before.do
just evaluates each argument and returns the value of the last one.
P.S.
I hope this introductory tutorial has piqued your interest in Clojure and functional programming. In upcoming tutorials we will examine more unique and powerful features of this language.
You can find complete project in my GitHub repository.
Posted on Utopian.io - Rewarding Open Source Contributors
Congratulations! Your post has been selected as a daily Steemit truffle! It is listed on rank 14 of all contributions awarded today. You can find the TOP DAILY TRUFFLE PICKS HERE.
I upvoted your contribution because to my mind your post is at least 17 SBD worth and should receive 70 votes. It's now up to the lovely Steemit community to make this come true.
I am
TrufflePig
, an Artificial Intelligence Bot that helps minnows and content curators using Machine Learning. If you are curious how I select content, you can find an explanation here!Have a nice day and sincerely yours,
TrufflePig
So glad someone is doing clojure tutorials. Its an underrated language that has a great developer store (aside from the compiler).
Thanks @tensor, I really appreciate your response. To be frank, I had some doubts whether tutorial on such a niche (relatively speaking) language will be of interest to Untopian audience. Glad I'm not alone in thinking clojure is under-appreciated.
Its funny, when I started programming, I started with C/C++ mainly, but then I worked with Common Lisp and Scheme over the years (I still primarily use Emacs). When Clojure came out I was ecstatic and I honestly thought that this time, it would break into the mainstream (Lisp that is) especially with ClojureScript and what it offers. Even though it didn't really do that, I still find that having learned and worked with various Lisps just makes me a much better developer. I make it a point to tell all of my students and co-workers that they should learn Clojure or Scheme if they really want to master programming.
In a roundabout way, I am saying that your tutorials are necessary even if there isn't a huge audience. If even one or two JavaScript devotees decides to learn Clojure from your tutorials, then they will most likely, end up becoming better programmers.
As a follower of @followforupvotes this post has been randomly selected and upvoted! Enjoy your upvote and have a great day!
Thank you for the contribution. It has been reviewed. I'm waiting for the next part in this curriculum.
Need help? Write a ticket on https://support.utopian.io.
Chat with us on Discord.
[utopian-moderator]
Hey @laxam I am @utopian-io. I have just upvoted you!
Achievements
Utopian Witness!
Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x