First steps in Clojure
16 Nov 2015I remember the first time I had to deal with Clojure. I just came back after a job interview and I was asked to deliver a solution on a functional language to an exercise. Naturally, I opened a terminal and typed:
$ brew install clojure
brew
is the package manager that install open source stuff on OS X. But instead of installing
Clojure, homebrew returned this error:
Error: No available formula with the name "clojure"
Clojure isn't really a program but a library managed as part of a
project and Leiningen is the user interface to that library.
To install Clojure you should install Leiningen:
brew install leiningen
and then follow the tutorial:
https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md
Unlike Ruby or Python, Clojure does not have a native compiler for OSX or Linux: it runs on the Java Virtual Machine (also called JVM). So, you don’t need to install a compiler, you just need to install Java, download a JAR and you should be ready to run Clojure.
Running Clojure from the JAR
But wait, the error said that I need to install Leiningen? Yes, we will get on Leiningen on a moment. But, for now let’s run Clojure directly from a JAR. You can download the most recent version of the Clojure JAR from this website: http://clojure.org/downloads and unzip it. Then, you can run on a terminal:
java -cp clojure-1.7.0.jar clojure.main
This -cp
option allows you to specify a list of directories, JAR archives and ZIP archives to the Java application launcher, the other argument clojure.main
will be the namespace whose function -main
will be called. You can also run a Clojure file like a script by passing the Clojure source file as another argument. To make it happen, I’m adding this content to a file called hello_world.clj
:
(println "Hello world")
And I will run:
java -cp clojure-1.7.0.jar clojure.main hello.clj
And now you have run your first Clojure program. It is a simple program, the only dependency is on Clojure language. What if I want to write a program that needs to do an HTTP request? HTTP Kit is probably the most popular HTTP client in Clojure (and also can be used as a Server). Let’s write a code to do a HTTP request:
(require '[org.httpkit.client :as http])
(println @(http/get "https://api.github.com/users/octocat/orgs"))
For now, no need to focus on the code, just trust me that it will work: I will explain what is this @
later.
To run it, you will need something more: you will need to specify the http-kit JAR.
java -cp clojure-1.7.0.jar:http-kit-2.1.19.jar clojure.main hello.clj
And voilá, it should work.
Leiningen
Definitely this is not a straightforward way to run code. You will need to manually download JARs, and remember to insert all of them on your command to run your program. Happily, Leiningen can help us to make it really easy to bootstrap a Clojure application, to manage the dependency and to create different profiles (think of a profile as a “different” way to run your application). If you’re familiar with Ruby on Rails, it does basically what Bundler (dependency management), Rake (make-like tool to run different profiles of your application) and Thor (code bootstrap generator). It also compiles your Clojure code into JVM bytecode and also can create a JAR file for you to export it. If you wabt to a comprehensive explanation on everything that Leiningen does, you can check this excellent article about it.
So, to bootstrap a Clojure application with Leiningen you first need to install Leinigen: you can do so by downloading a script, through a package manager or if you’re a M$ Windows user, you can use a installer.
After installing it, you should be able to run
lein new app blog-example
lein
is the command to invoke Leinigen, new
tells Leinigen to bootstrap a new project, app
is the template (this argument is optional and it defaults to a library project) and finally blog-example
is the name of the application we’re bootstrapping.
It should have generated something similar to this:
CHANGELOG.md README.md project.clj src
LICENSE doc resources test
./doc:
intro.md
./resources:
./src:
blog_example
./src/blog_example:
core.clj
./test:
blog_example
./test/blog_example:
core_test.clj
To build our hello world application, let’s for now focus on two files project.clj
and src/blog_example/core.clj
. project.clj
is the project declaration: it contains the configuration that will be used to run your project, the name, description, the license information, etc.
(defproject blog-example "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.7.0"]]
:main ^:skip-aot blog-example.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
Let’s focus on two things on this file: :dependencies
and :main
: the first one is a vector (for now, think of vectors as the same as what is called as an array: I will explain more about sequences later) of all the dependencies along with the versions, and :main
contains the namespace that will be run when you run lein run
: the function that will actually run is the one called -main
. Let’s take a look at the file core.clj
in the directory src/blog_example
:
(ns blog-example.core
(:gen-class))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))
The -main
only contains a println
call that outputs "Hello World"
, and this function will be called when you call lein run
.
The coolest thing about Leiningen is that you don’t even need to install Clojure to run the project: just install the java, Leiningen and the first time you run the program, it will install all the dependencies (including Clojure). Also, you don’t even need Leiningen to run your program: you can easily pack it with lein uberjar
and run with java -jar jarfile.jar
and it will run your Clojure program. So, to summarize Leiningen helps a lot.
If you want to add the http-kit
you can add it to the dependencies vector:
lein deps
only install the dependencies: but if you run lein run
and there is a missing dependency it will install it anyway.
Last, you can add the same code inside the -main
function:
(ns blog-example.core
(:require [org.httpkit.client :as http])
(:gen-class))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!")
(println @(http/get "https://api.github.com/users/octocat/orgs")))
And then running lein run
will actually do the request.
Missing Parts
I know: I’ve skipped some parts to make your understanding of this basic tutorial easier. I will make a quickly explanation here of those parts:
-
@
signal on http call: this is related to the asynchronous nature of HTTP Kit: a call without to thehttp/get
function will be asynchonous and a callback can be passed as an argument and the code inside the callback will be run once the request returns something. Using@
actually turns it synchronously: it will do the request and wait the result. This construction is called promise and I will cover it later. -
project.clj
: besides your app metadata like description and license, it also includes the dependencies, where your compiled code will be generatedtarget-path
, the main function (:main
) that will be called onlein run
and the profiles: that are different possible switches that you can enable or disable: like testing libraries, debug libraries and so on: by default the only profile tells that when generating anjar
through theuberjar
command it should do the ahead of time compilation. Remember this is just a subset of all possible options for a aProject.clj
: it has lot of possible options and configurations and all of them are explained here. -
core.clj
:ns
is the namespace declaration: it contains what namespaces should also be required and may also contain agen-class
that will generated a named Java class: this is useful here because the JVM requires apublic main
method and thisgen-class
directive will generate this method. Last, the-main
function contain an& args
as argument: it will contain the arguments given on the command line this&
is actually a way to tell a clojure function that the function may receive an arbitrary number of arguments (besides the ones declared before the&
) and will be stored on avector
.
Summary
So I hope you learned a few things here:
- How to “install” Clojure
- How to run Clojure code only with Java
- How to install Leiningen
- How to run code inside Leiningen
- How to add a new dependency
- How to require it on your library
Next time I will speak more about the Clojure ecosystem: what they are and how they work:
- Where to look for Libraries;
- Editor recommendations;
- Project.clj overview;
- Core.clj overview;