Book Image

Clojure Programming Cookbook

Book Image

Clojure Programming Cookbook

Overview of this book

When it comes to learning and using a new language you need an effective guide to be by your side when things get rough. For Clojure developers, these recipes have everything you need to take on everything this language offers. This book is divided into three high impact sections. The first section gives you an introduction to live programming and best practices. We show you how to interact with your connections by manipulating, transforming, and merging collections. You’ll learn how to work with macros, protocols, multi-methods, and transducers. We’ll also teach you how to work with languages such as Java, and Scala. The next section deals with intermediate-level content and enhances your Clojure skills, here we’ll teach you concurrency programming with Clojure for high performance. We will provide you with advanced best practices, tips on Clojure programming, and show you how to work with Clojure while developing applications. In the final section you will learn how to test, deploy and analyze websocket behavior when your app is deployed in the cloud. Finally, we will take you through DevOps. Developing with Clojure has never been easier with these recipes by your side!
Table of Contents (16 chapters)
Clojure Programming Cookbook
Credits
About the Authors
About the Reviewer
www.PacktPub.com
Preface

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