% Training day
https://github.com/iloveponies/training-day
If you followed through the instructions in the last section of the previous chapter, you already have this repository forked, cloned and ready to go. If you missed the instructions, here is a link.
When in doubt, do exactly the opposite of CVS. Linus Torvalds
You should now have a directory called training-day
. This directory contains
a Leiningen project we have lovingly hand-crafted just for you. It has unit
tests for the exercises, more about these in a minute. Lets first focus on the
stuff inside src
.
If you are using Light Table and you followed the instructions from the last
chapter, you should have an Instarepl open for the training-day
project. If
you don't, now is a good time to go through those instructions.
If you are using some other editor, just open the file
src/training_day.clj
.
Inside the file src/training-day.clj
are stubs for all the exercises
that require coding. For example this stub:
(defn square [n]
":(")
When you are solving the exercises, you are to modify these stubs and run the tests to see if you got it right.
Speaking of testing, let's run the unit tests now. This will output a lot of somewhat superfluous information while Leiningen downloads the project dependencies, so you will see more output printed than what is shown below. This output is printed only once, so subsequent runs will not be so chatty.
me@my-computer:~$ cd training-day/
me@my-computer:~/training-day$ lein midje
Leiningen tells us it's downloading the whole internet:
Could not find metadata lein-midje:lein-midje:2.0.0-SNAPSHOT/maven-metadata.xml in central (http://repo1.maven.org/maven2)
Retrieving lein-midje/lein-midje/2.0.0-SNAPSHOT/maven-metadata.xml (1k)
from http://clojars.org/repo/
…Skip…
And finally, the output we are interested in:
FAIL "answer" at (training_day_test.clj:6)
Expected: 42
Actual: ":("
FAIL "square" at (training_day_test.clj:9)
Expected: 4
Actual: ":("
FAIL "square" at (training_day_test.clj:10)
Expected: 9
Actual: ":("
FAIL "average" at (training_day_test.clj:13)
Expected: 3
Actual: ":("
FAIL "average" at (training_day_test.clj:14)
Expected: 3/2
Actual: ":("
FAILURE: 5 facts were not confirmed.
This tells us that all the test failed. Which was expected, as you haven't done any of the exercises yet.
Our project uses the Midje testing library. To ease distributing changes to the tests, the actual test code is in a separate project. The easiest way to look at the code is to use Github. For every chapter there is a repository with the same name and a "-tests" suffix. This repository is where the tests live. Here's a link to the file containing the tests for this chapter. Just click it. In the file are blocks of code that look like this:
(facts "square"
(square 2) => 4
(square 3) => 9)
The facts
form declares some facts of the square
function. A fact,
in Midje, is an expression expr => expected-value
, saying
"Evaluating expr
should return expected-value
". Our two tests (or
facts) say that (square 2)
should return 4
and (square 3)
should
return 9.
Our stub for square
simply returns the string ":("
for all values
of n
. This does not pass the tests, which we saw above. The relevant
output was:
FAIL "square" at (training_day_test.clj:9)
Expected: 4
Actual: ":("
FAIL "square" at (training_day_test.clj:10)
Expected: 9
Actual: ":("
The FAIL
lines indicate that our stub function fails the tests as
expected, because the string ":("
is not 4
or 9
.
Run lein midje
often to see if your code is working or not. You can
also run lein midje :autotest
to start a loop that runs the tests
every time you make changes to the code.
One of the nice features of Clojure is the REPL. It is an interactive session in which you can write code and see it executed immediately. Here you have two choices. Light Table has a really nice REPL called Instarepl, and Leiningen provides the classical REPL. We encourage you to test out Instarepl even once. It's pretty awsome.
Follow the installation instructions found in the previous chapter if you
haven't already. In Linux, you run the file LightTable
as you would run any
binary. The previous chapter has instructions for opening an instarepl.
If you don't want to use Light Table, there is always the lein repl
.
Issue that command in the terminal and an interactive Clojure session
starts. It should look like this:
me@my-computer:~$ lein repl
nREPL server started on port 50443
Welcome to REPL-y!
Clojure 1.5.0
Exit: Control+D or (exit) or (quit)
Commands: (user/help)
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
(user/sourcery function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Examples from clojuredocs.org: [clojuredocs or cdoc]
(user/clojuredocs name-here)
(user/clojuredocs "ns-here" "name-here")
user=>
If you type (+ 1 2)
and press the return key, you should see this:
user=> (+ 1 2)
3
user=>
Clojure evaluated the expression (+ 1 2)
and printed its value, 3
.
If you see something different, please let us know by raising your
hand.
In our example code, we often want to show the result of an expression when it is evaluated. Instead of showing what evaluating the expression in the interactive session looks like:
user=> (+ 3 4)
7
We're going to use the convention of writing the expression and the
result, separated with ;=>
. Quite like how Light Table does it. For
an example:
(+ 3 4) ;=> 7
Sometimes we will put the result on a new line:
(str 1337)
;=> "1337"
(str "Over " 9000 "!") ;=> "Over 9000!"
When the resulting value is too long to display on one line, we will mark the
continuation lines with a leading ;
like this:
(take 20 (cycle ["foo" "bar"]))
;=> ("foo" "bar" "foo" "bar" "foo" "bar" "foo" "bar" "foo" "bar"
; "foo" "bar" "foo" "bar" "foo" "bar" "foo" "bar" "foo" "bar")
;
starts a comment that lasts until the end of that line, like //
in Java. The =>
inside the comment is an illustration of an arrow,
meaning "evaluates to". You can copy the examples above to the REPL
and they will work without modification:
user=> (+ 3 4)
7
user=> (+ 3 4) ;=> 7
7
user=> (+ 3 4) ; I am a comment
7
As you can see above, instead of writing 1 + 2
to calculate the sum
of one and two, we write (+ 1 2)
. This syntax applies everywhere in
Clojure. In fact, Clojure has no operators at all. In languages such
as Java or C, arithmetic operations are usually written in the
mathematical notation called infix form. Clojure, on the other hand,
uses prefix form for its syntax. The next table shows what
mathematical expressions look like in these two syntaxes.
Java Clojure
2 + 3
(+ 2 3)
42 * 7
(* 42 7)
2 - 78 * 35
(- 2 (* 78 35))
1 + 2 + 3 + 4
(+ 1 2 3 4)
Let's input these definitions in our Clojure session to see how they work:
user=> (+ 2 3)
5
user=> (* 42 7)
294
user=> (- 2 (* 78 35))
-2728
user=> (+ 1 2 3 4)
10
The arithmetic operations have some special properties. Everyone of the operations works with only one operand.
(+ 1) ;=> 1
(* 2) ;=> 2
(- 3) ;=> -3
(/ 4) ;=> 1/4
This behavior might seem odd, but here is the catch. The arithmetic
operations above are, in fact, function calls. That is, +
is
actually a function (called +
), as are *
and -
. Don't believe
use? Write just +
in the REPL. In Instarepl you see
+ => fn
and in lein repl
user=> +
#<core$_PLUS_ clojure.core$_PLUS_@2d21471c>
They are both telling you that +
is just a function.
All function calls in Clojure look the same: (function-name argument-1 argument-2 ...)
. As an example of a non-arithmetic
function, let's take a look at getting a single character from a
string in Clojure and Java. In Clojure, we can use the get
function
for this:
(get "Clojure" 2) ;=> \o
The result is the character o
, printed in Clojure's literal character
syntax. (That is, \o
in Clojure code means the single character o
. In
Java, you would write a literal character as 'o'
.)
In Java, we reorder things a bit: the first parameter goes before the method name, and the parentheses are moved after the method name:
"Java".charAt(2); //=> 'v'
In Clojure, the function name always goes first, and the parameters come after it, including the object, if such is present. The Clojure syntax might take some time to get used to, but becomes natural after you've written a few Clojure programs.
Write a Clojure expression in the REPL that, using `get`, gets the first character in the string `"abrakadabra"`. This exercise does not give any points and you do not need to return it.So far we've worked with expressions and called some existing functions. For structuring any kind of non-trivial programs, we will want to group code into our own functions.
Lets start writing a function (hello who)
, which returns an English
greeting for the user. Functions are created with fn
. Write the
following in you REPL and evaluate it.
(fn [who] (str "Hello, " who "!"))
The REPL should tell you that it was a function. Instarepl just says
(fn [who] (str "Hello, " who "!")) fn
As usual, lein repl
is a bit more verbose and states
user=> (fn [who] (str "Hello, " who "!"))
#<user$eval326$fn__327 user$eval326$fn__327@4a2d09aa>
So what kind of a function did we actually get? Inside the square
brackets are the parameters of the function. This one only has a one
and we gave that a name who
. Right after that comes comes an
expression, a call to a function str
in this case. The value of this
expression will become the return value of this function. In general,
the return value of a function will be the value of the last
expression in the function.
So we got a function. Lets call it!
((fn [who] (str "Hello, " who "!")) "Jani") ;=> "Hello, Jani!"
That worked just like with +
and the others. First comes the
function, then the arguments. In this case the function does not have
a name so we need to write the whole definiton. We also have only one
argument, "Jani"
.
(fn [name] (str "Welcome to Rivendell mr " name))
This exercise does not give any points and you do not need to return it.
Now we know how to make a function, but we only got a glimpse of it
and then it was gone. We have to write the definition of the function
every time we want to call it. But we want something more permanent,
something that we can write once and call multiple times! The
functions created with fn
are called anonymous functions. They are
called that because they have no name. To give a name to a function we
can use def
. Let's give the greeter function a name right away.
(def hello (fn [who] (str "Hello, " who "!")))
That definition is also an expression, so don't forget to evaluate it.
Now we can call this function. Write (hello "beautiful")
in your
REPL to get a instant compliment. In the name of sex-equality evaluate
also (hello "handsome")
.
So what just happened? Well, def
gives a name to a value. In the
previous case the value is what we get when we evaluate (fn [who] (str "Hello, " who "!"))
. And what do we get when we evaluate that? A
function. So we gave the name hello
to a function that gives out
greetings.
aswer ;=> 42
Anonymous functions have their uses in functional programming. So it
is nice to know that we can create them with fn
. But most of the
time we want to give the function a name right away. To make that a
bit easier, we have defn
. Here is how to create and name the
previous function with defn
. There is a running commentary
alongside, to make sure we understand its parts.
(defn ; Start a function definition:
hello ; name
"Gives out personalized greetings." ; a optional docstring
[who] ; parameters inside brackets
(str "Hello, " who "!")) ; body
Here hello
is the name of the function, [who]
is the parameter
list, and the expression on the second line is the body of the
function. The return value of the function is the value of the last
expression inside the function body. In this case, it is the value of
the (str "Hello, " who "!")
expression. We have also provided an
docstring that briefly tells what this function does. This is
optional, but like washing your hands after visiting the toilet,
highly recommended.
For comparison, our function looks like this in Java:
/**
* Gives out personalized greetings.
*/
String hello(String who) {
return "Hello, " + who + "!";
}
Note that in Clojure, there is no return
keyword. The return value
of a function is always the value of the last expression in the
function body.
user=> (use 'clojure.repl)
user=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +'
nil
You can also see the docstring of our hello
function. Evaluate the
following in your REPL.
(use 'clojure.repl)
(doc hello)
You can also use (user/clojuredocs function)
to see some examples
for function
. This should work for most of the built-in functions.
user=> (user/clojuredocs min)
========== vvv Examples ================
user=> (min 1 2 3 4 5)
1
user=> (min 5 4 3 2 1)
1
user=> (min 100)
100
========== ^^^ Examples ================
1 example found for clojure.core/min
nil
The next section will tell you more about the function use
.
Code in Clojure projects is structured into namespaces defined in
files. Usually each file corresponds to a one namespace identified by
the file's path. For an example, the file foo/bar/baz.clj
could
contain the namespace foo.bar.baz
. This is slightly different from
Java, where directories correspond to namespaces (packages) and files
under a directory usually contain a single class in the given package.
The repository that you cloned at the end of the last chapter contains
an Leiningen project. Inside the src
directory are all the code
files of the project. The file training_day.clj
should begin with
the following.
(ns training-day)
This is a namespace definition. It tells us that the code in this file
is in the namespace training-day
. There are no dots in the namespace
name as there is no directories under src
, just the lonely file
training_day.clj
.
See how the namespace is called training-day
, but the file is
training_day.clj
? This is intentional. If a namespace name has an
hyphen, the corresponding file name should have an underscore.
We are at cross roads. Choose to right set of instructions based on your REPL.
You need to first connect to the project training-day
. When you then open an
instarepl, LigthTable should ask you to which project you want to connect.
Select training-day
. Instructions for all this are in the
previous chapter.
Navigate to the directory training-day
, and run lein repl
.
Now as you have your REPLs open, write the following in it.
(use 'training-day)
hai
Were you greeted in all CAPS? If not, please raise your hand.
What use
did was that it looked inside the namespace training-day
and brought with it all the names defined in that namespace. The name
hai
was one of them.
user=> (use example.hello)
java.lang.ClassNotFoundException: example.hello (NO_SOURCE_FILE:1)
'
is an alias for the quote
special form, which we will talk more about
later.
Here are two exercises more to keep your fingers warm.
Write the function `(square x)` that takes a number as a parameter and multiplies it with itself.(square 2) ;=> 4
(square 3) ;=> 9
(average 2 4) ;=> 3
(average 1 2) ;=> 3/2
Now would be the time to submit your solutions to be graded. Run lein midje
to see if all the tests pass. If the do, you should see the
following:
me@my-computer:~/training-day$ lein midje
All claimed facts (5) have been confirmed.
Don't worry if you haven't figured out all the exercises. You can still submit those that you have successfully made. Here is how to do it.
-
Create a commit of your changes by running
git commit -a -m "message here"
-
Update your fork in Github by pushing the changes. This will ask for your Github login.
git push
-
Go to the Github page of your fork of the repository
training-day
. Click on the greenCompare & review
button. When you are ready, clickSend pull request
.
If you didn't submit the solutions all at once, or some of the submited ones were incorrect, you can re-submit as many times as you need to. Just fix the code and follow the steps 1 and 2 again. When you push, the open pull request is updated.