Programming in Clojure. Part 1: Why clojure

in #utopian-io4 years ago (edited)

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.

Clojure venn diagram

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 cases
  • testing used to split the group into smaller sub-groups
  • is is a generic assertion macro. Test fails if it's argument in false.

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 to println, 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 with defn, 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 calls play recursively, with same secret and number of tries increased by one.
  • cond is similar to multiple if-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 call wrong 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 calling play 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

Sort:  

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
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

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

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

Coin Marketplace

STEEM 0.29
TRX 0.06
JST 0.039
BTC 35389.82
ETH 2425.31
USDT 1.00
SBD 4.01