Using third-party libraries
So, you have found someone else's code that you would like to use, and you are trying to take their work and use it in your project, great! This is what this recipe is all about. There are a few ways to get the code closer to you.
Getting ready
This recipe will introduce you to the art of adding dependencies, packaged as JAR files, to your project and how to reference them, as well as using them from your Clojure code. We will go from downloading the file and starting Clojure REPL manually, to using dependency management tools. Lastly, we will also present how to add new dependencies at runtime, so you do not need to restart your live programming environment.
How to do it...
Each minor section in this recipe shows you how to add the dependency, using different ways in different scenarios.
Adding the JAR file manually to your classpath
Say we have found the following library, clj-tuples
, and we want to add this to our REPL session. Most JARs for Clojure are available on either mvnrepository.com or clojars.org.
clj-tuples
is on clojars
, and since clojars
is a regular Maven repository, we can navigate directly through the file. Download and save it (https://clojars.org/repo/ruiyun/tools.timer/1.0.1/tools.timer-1.0.1.jar), and now let's start a Clojure REPL:
java -cp .:clj-tuple-0.2.2.jar:clojure-1.8.0-beta1.jar clojure.main
And let's quickly have fun with our new library code...mmmmm, tuples:
user=> (use 'ruiyun.tools.timer) ; nil user=> (run-task! #(println "Say hello every 5 seconds.") :period 5000); #object[java.util.Timer 0x45a4b042 "java.util.Timer@45a4b042"] user=> Say hello every 5 seconds. user=> (cancel! *1)
Ok, great!
Using Leiningen and a project.clj file
So, that was fun, but maybe you have more than one person's code you want to steal, and also, you just noticed that some of the stolen code is also stealing code from somebody else's code...what do you do?
Leiningen is a command-line tool that will, among other things, help you maintain stolen code. This recipe will not look into installing Leiningen because there is documentation all around that does this, so we just want to make sure at this stage that you have a recent version:
NicolassMacBook:chapter01 niko$ lein version
Leiningen 2.5.1 on Java 1.8.0_45 Java HotSpot(TM) 64-Bit Server VM
To make Leiningen understand what we want, most of the time, we would give it a project.clj
file with a DSL that looks mostly like a gigantic Clojure map. If we want to import the same dependency as we did previously, this is the way we would write it:
(defproject chapter01 "0.1.0" :dependencies [[org.clojure/clojure "1.8.0-beta1"] [clj-tuple "0.2.2"]])
At its root, declaring a dependency on a third-party library is done through this mini DSL, where a two element vector points to a name and a version:
[name "version"]
So here:
[clj-tuple "0.2.2"]
Dependencies are declared as Clojure vectors in the project map, with :dependencies
as its key. We now have access to billions of libraries of somewhat different quality depending on the author, but anyway, it's done. Leiningen also uses clojars
by default, so there's no need to define repository definitions yet. The same code as before works:
(use 'clj-tuple)
Viewing dependencies
Using the project.clj
file, we can directly see what our code depends on, and in particular the version of things our code depends on. The project.clj
file of clj-tuple
is located at https://github.com/Ruiyun/tools.timer/blob/master/project.clj and the portion we are interested in is as follows:
(defproject clj-tuple "0.2.2" :description "Efficient small collections." :dependencies [] ...)
This means that clj-tuples
does not depend on anything else and is a well behaved self-contained library.
This is obviously not always the case, and while we are at it we can look at another library, named puget
.
Puget has the following dependencies defined:
:dependencies [[fipp "0.6.2"] [mvxcvi/arrangement "1.0.0"] [org.clojure/clojure "1.8.0"]]
But the great thing is that we don't need to write all those dependency lines ourselves. Transitive dependencies are pulled properly by Leiningen, so our project.clj
file will now look simply like this:
(defproject chapter01 "0.1.0" :dependencies [ [org.clojure/clojure "1.8.0-beta1"] [clj-tuple "0.2.2"] [mvxcvi/puget "0.9.1"]])
The first time we launch a new REPL we will notice all the dependencies being downloaded:
NicolassMacBook:chapter01 niko$ lein repl Retrieving mvxcvi/puget/0.9.1/puget-0.9.1.pom from clojars Retrieving fipp/fipp/0.6.2/fipp-0.6.2.pom from clojars Retrieving org/clojure/core.rrb-vector/0.0.11/core.rrb-vector-0.0.11.pom from central Retrieving mvxcvi/arrangement/1.0.0/arrangement-1.0.0.pom from clojars Retrieving org/clojure/core.rrb-vector/0.0.11/core.rrb-vector-0.0.11.jar from central Retrieving fipp/fipp/0.6.2/fipp-0.6.2.jar from clojars Retrieving mvxcvi/arrangement/1.0.0/arrangement-1.0.0.jar from clojars Retrieving mvxcvi/puget/0.9.1/puget-0.9.1.jar from clojars
This only applies the first time. The second time it occurs without extra messages, and you can now check for yourself that the library is there, and you get the expected colorized output, but not in this book:
user=> (require '[puget.printer :as puget]) nil user=> (puget/pprint #{'x :a :z 3 1.0}) #{1.0 3 :a :z x} nil user=> (puget/cprint #{'x :a :z 3 1.0}) #{1.0 3 :a :z x}
one-off
Sometimes it is great to just try things out, and have a one-off REPL to try out the new dependencies. This is where we can use a Leiningen plugin named try
.
Plugins for Leiningen can be installed globally on your machine with a file named profiles.clj
located here:
$HOME/.lein/profiles.clj
The file is a simple map with user-defined settings; here, we just want to add a plugin, so we add it to the vector:
{:user {:plugins [ [lein-try "0.4.3"] ]}}
That's it. From anywhere on your computer you can now try dependencies. To make things new, we will look at a new useful dependency named env
, which makes it easy to retrieve environment settings.
This is the usual Leiningen definition of env
, and the following code would be used in project.clj
, as seen before:
[adzerk/env "0.2.0"]
Here, we just type the following:
lein try adzerk/env
And we can see the dependencies coming along locally (provided you have an Internet connection):
Retrieving adzerk/env/0.2.0/env-0.2.0.pom from clojars Retrieving adzerk/env/0.2.0/env-0.2.0.jar from clojars
And we can now use the require
macro:
(require '[adzerk.env :as env])
And try it. env
returns all the env
variables available, whether through Java or Shell:
user=> (env/env) ; ...
If you have the chance to run the preceding command where project.clj
was located, the dependencies from the project are also available, so we can combine dependencies:
user=> (require '[puget.printer :as puget]) user=> (puget/cprint (env/env)) {"Apple_PubSub_Socket_Render" "/private/tmp/com.apple.launchd.jI7P2DRL6X/Render", "HOME" "/Users/niko", "JAVA_ARCH" "x86_64", "JAVA_CMD" "java", "JAVA_MAIN_CLASS_3927" "clojure.main", "JVM_OPTS" "", ...
Voila! Now we have combined a temporary dependency with our main project dependencies.
New dependencies at runtime
So far we have seen how to add dependencies offline, in the sense that we need to stop our REPL in order for the new dependencies to be handled properly. Now we will see how to add a dependency on the fly.
The dependency we will look at now is named pomegranate
and it does just that, add dependencies on the fly.
In the same way we added a Leiningen plugin earlier on, we will add a global dependency to our runtimes by adding a dependency to the same profiles.clj
file:
{:user {:dependencies [[com.cemerick/pomegranate "0.3.0"]] }}
Those dependencies will be ready to be loaded through all your Leiningen-based projects, so be careful not to add too many. With a new REPL loaded, we can now load pomegranate
, and the only method we need is happily named add-dependencies
:
(require '[cemerick.pomegranate :refer [add-dependencies]])
The newly required function takes a vector of coordinates using the same pattern we have seen so far, and a map of repositories, with a name for the key and a URL to a Maven-like repository for the value:
(add-dependencies :coordinates '[[active-quickcheck "0.3.0"]] :repositories {"clojars" "http://clojars.org/repo"})
The first time this is called, the dependency and all the underlying will be downloaded and be ready for use. We took active-quickcheck
as a sample, a library that can be used to generate random tests based on some given predicates. The library still needs to be required in the current namespace:
(use 'active.quickcheck) ; sample dependency test, we will not go in the details here (quickcheck (property [a integer b integer] (= (+ a b) (+ b a))))
So we have pretty much seen all the different ways to add dependencies to our Clojure environment, whether through:
- Direct JAR file
- Leiningen's
project.clj
- Leiningen's
profiles.clj
- Leiningen's
try
plugin - Using
pomegranate