Your first Clojure code
In this post I want to give a very quick tour of Clojure. This is aimed at people that might not have done any Lisp, Java or Functional programming before. Of course it's not going to be complete but should be enough to get you to write a few bits of code and perhaps solve some puzzles.
Installation
If you're on Mac and use homebrew, just do brew install clojure
. Otherwise check out the official instructions.
Check that your installation works fine by typing clj
— you should see something like:
Clojure 1.9.0
user=>
Hit Ctrl-C
to exit this.
Note: you will notice that it takes a few second to get to this prompt. You will suffer through this during this introduction, but when doing proper Clojure development, the startup time becomes a non-issue for most workflows.
Hello world!
Create a file named hello.clj
, with these contents:
(println "Hello, world!")
You can run it with clojure hello.clj
, and you should see the results printed on your terminal.
So what's going on? println
is a function. "Hello, world!"
is a String
. You invoke a function by enclosing it and its arguments with parentheses. In other languages, you'd do println("Hello world!")
which is a bit similar.
Clojure being a dynamic language, you can pass any kind of argument to println
— try adding those to hello.clj
and invoke it again with clojure hello.clj
.
(println 123) ;; this is a comment
(println true) ;; true/false are booleans
(println nil) ;; nil is null
(println [1 2 3]) ;; vector definition, commas are optional
You should see this:
Hello, world!
123
true
nil
[1 2 3]
So a .clj
file can contain one or more top-level expressions. They will be evaluated one by one, top-to-bottom, as in most programming languages. Clojure also has numbers, booleans, nil and a few pretty cool container data structures.
The REPL
Clojure also an interactive prompt; you saw it when you run clojure
without a command. You can try all this code in the REPL, like so:
$ clj
Clojure 1.9.0
user=> (println "hi again")
hi again
nil
user=>
This time, you see the printed line, but you also see nil
— this is because the REPL will print out the return value of each expression as you type it in. In this case, the println
function returns nil.
There is an alternative form of println
called println-str
— instead of printing directly, it will instead return a string. Let's try it out:
user=> (println-str "another try")
"another try\n"
user=>
This time, nothing is printed, and we just get a string back. You can see the newline at the end.
Note: moving on, I'll showcase REPL-based code, but you can run all those just by adding them to your hello.clj
file, and running clojure hello.clj
. Note how we use clojure
to run programs but clj
to get the REPL.
Variables
Let's try to capture the value returned by println-str
and print it later:
user=> (def a "hello")
#'user/a
user=> a
"hello"
user=> (println a)
hello
nil
user=>
Notice how to define a variable, we just call a function — this time the function is def
and takes two arguments: the variable name which is a Symbol
, and the variable value, which can be anything. Notice how def
also returns this weird-looking #'user/a
thingy — this is the internal representation of "global" variables in Clojure. However, when you evaluate the Symbol
, that is, a
, you get the value back, which is "hello"
. You can use the name of the variable as you would expect when calling functions etc.
Of course, the value of the variable can be pretty any expression — in this case, the expression will be evaluated, and its return value will be used:
user=> (def my-var (println-str "variables are cool"))
#'user/my-var
user=> my-var
"variables are cool\n"
user=> (println my-var)
variables are cool
nil
user=>
Notice how we now get two newlines when using println
— the one that my-var
contains, and another one that println
adds.
Also notice how my-var
can contain a dash. Clojure is very permissive on what can be used in identifiers (Symbol
s, technically). You might have guessed that already, since println-str
contains a dash.
Note: println-str
is a very little used function, but makes sense for this tutorial. We'll cover more useful functions soon!
Functions
Let's define our own function — it will just call the only two functions we have used so far:
user=> (defn say-it*3 [x]
(println x)
(println x)
(println x))
#'user/say-it*3
user=> (say-it*3 "hi!")
hi!
hi!
hi!
nil
user=>
Let's break this apart — to define a function, just call another function, in this case defn
.
Now, defn
is technically a macro, but it looks and behaves just like a function for our purposes. Its arguments are:
- a
Symbol
— the name of the function,say-it*3
(wow! you can have a*
in your function name!) - a vector of arguments, that is,
[x]
, meaning "this functions takes a single argument namedx
- a bunch of expressions, that when the function is called, will be evaluated top-to-bottom. The return value of the last expression will be returned (there is no
return
or similar keyword).
As you can see, defn
returns the same kind of var
as def
returns.
You call this function as if it was any other function, by surrounding it and its arguments with parentheses. Of course, functions can have more than one arguments:
user=> (defn another! [a b c]
(println a)
(println b)
(println c))
#'user/another!
user=> (another! 1 2 3)
1
2
3
nil
user=>
...or even no arguments:
user=> (defn -*- []
(println "konnichi-wa!"))
#'user/-*-
user=> (-*-)
konnichi-wa!
nil
user=>
And yes, you can have a lot of weirdly named functions — please don't do it :)
The fact that you can use a lot of characters for symbols comes directly from Clojure's minimal syntax — there are very few special operators and rules, so even arithmetic stuff is done by just calling functions:
user=> (+ 1 2)
3
user=> (* 3 4)
12
user=> (+ 1 2 3 4 5)
15
user=> (= 1 2)
false
user=> (= 2 (+ 1 1))
true
user=> (= 2 2 2)
true
In this case +
, *
and =
are just plain functions, and you call them with as many arguments as you want.
You can also have anonymous functions, like so:
user=> (fn [x] (println x))
#object[user$eval183$fn__184 0x4bff64c2 "user$eval183$fn__184@4bff64c2"]
user=>
Note: Ignore the weird return value — this is a Clojure-internal representation.
See how fn
is really similar to defn
— you just don't give it a name. Let's combine fn
and def
to actually capture the function in a variable:
user=> (def anonymous (fn [x] (println x)))
#'user/anonymous
user=> anonymous
#object[user$anonymous 0x4566d049 "user$anonymous@4566d049"]
user=> (anonymous "hi")
hi
nil
user=>
Conditionals
The fundamental way to do conditionals is by calling the if
function:
user=> (if (= 1 2) "wat?" "ok!")
"ok!"
user=>
We call if
we 3 arguments:
- The condition, in our case, the result of
(= 1 2)
, i.e.false
- The "true" expression, i.e.
"wat?"
- The "false" expression, i.e.
"ok!"
The return value is either the "true" or "false" expression. Here's a more complicated example:
user=> (if (= 1 "1") (println "wat?") (println "ok!"))
ok!
nil
user=>
Notice how although only one expression is even evaluated — this is a special property of if
that is provided by the Clojure runtime. If if
was a normal function, we'd see both wat?
and ok!
printed.
Notice also that the return value is nil
, since this is what println
returns.
Another useful special function is do
:
user=> (do
(println 1)
(println 2)
(println 3)
"done!")
1
2
3
"done!"
user=>
This looks very similar to defn
and fn
— it evaluates expressions one-by-one, and returns the value of the last one (in our case, just the string "done!"
). By combining if
and do
you can get more useful constructs:
user=> (if (= 1 1.0)
(do
(println "Equality is weird")
"boo!")
(do
(println "Equality is sane")
"yay!"))
Equality is sane
"yay!"
user=>
By the way, if you are dealing only with numbers, you can use the ==
function instead of =
:
user=> (== 1 1.0)
true
Note: There are more conditionals available, but we'll stick with if
for a while
Scoping
Many programming languages have intricate rules about scoping — that is, from where you can access and change a variable. Clojure, on the other hand, is simple:
- Variables defined with
def
are always global - Variables defined with
let
are always local
Note: this is a simplification, but it can get you very far.
In reality, you always use def
sparingly, and always at the "root" level of a file. You use let
everywhere else. How does let
look like? It's just, as you may have guessed, a function:
user=> (let [local 1]
(println "This is my scope")
(println "The value is" local))
This is my scope
The value is 1
nil
user=>
Let looks a bit similar to defn
and fn
in that its first argument is a vector
, that is [local 1]
. The rest of the arguments are just expressions that are evaluated one-by-one, like in do
. The return value is, again, the value of the last expression, in our case nil
.
So how does this work? In other languages, you'd do local = 1
, then in the same "scope" you'd be able to use local
to refer to the value 1
. This is similar, the scope being the body of let. After the closing )
of let, local
is not valid anymore.
A more involved example:
user=> (let [a 1
b 2
c 3]
(* a b c))
6
user=>
Here we assign multiple variables, a
, b
, and c
at the same time. We just multiply them together, and we get the result back. Another one:
user=> (let [a 2
b 3
aa (* a a)
bb (* b b)
aabb (+ aa bb)]
(= aabb 13))
true
user=>
In this case, not only we assign multiple variables, we also use previously-assigned variables. The variable assignment is done top-to-bottom, so we just have to make sure everything is in the right order.
Functions everywhere
You might have noticed that to do very common stuff like defining a variable, a function, comparing or even adding two numbers together, we just call a function. This is a common attribute for any Lisp-like language, and there's no other syntax to them. In real-life usage though, there are a handful of functions that are so commonly used that they effectively become the syntax.
The everything-is-a-function is a bit of a simplification. Some things are macros, other things are special forms. But when you use them, usually you don't think about it, and you call them as if they were functions.
To be continued! Rather than let this sit in my drafts, I'm publishing it now in order to get some feedback. I will either add to this or post something new later.
Please post your feedback on this Reddit thread