Post navigation

Functional programming without feeling stupid, part 5: Project

In the last four installments of Functional programming without feeling stupid I’ve slowly built up a small utility called ucdump with Clojure. Experimentiing and developing with the Clojure REPL is fun, but now it’s time to give some structure to the utility. I’ll package it up as a Leiningen project and create a standalone JAR for executing with the Java runtime.

Creating a new project with Leiningen

You can use Leiningen to create a skeleton project quickly. In my project’s root directory, I’ll say:

lein new app ucdump

Leiningen will respond with:

Generating a project called ucdump based on the 'app' template.

The result is a directory called ucdump, which contains:

.gitignore README.md project.clj src/
LICENSE doc/ resources/ test/

For now I’m are most interested in the project file, project.clj, which is actually a Clojure source file, and the src directory, which is intended for the app’s actual source files.

Leiningen creates a directory called src/ucdump and seeds it with a core.clj file, but that’s not what actually what I want, for two reasons:

I want ucdump to be a good Clojure citizen, so I’m going to put it in a namespace
called com.coniferproductions.ucdump.

My Git repository for ucdump also contains the original Python version of the application, which is in <project-root>/python, and I want the Clojure version to live in <project-root>/clojure.

So first I’ll rename the ucdump directory created by Leiningen to clojure:

mv ucdump clojure

Then I’ll make the namespace directories and rename core.clj to udump.clj:

There are some namespace references in the source files created by Leiningen which are now obsolete, so I’ll fix them eventually, but I’ll focus first on the project file. At this point it looks like this:

You can read up on the settings in the Leiningen tutorial. These are suitable for a standalone application, but the actual values still need to be fixed. When I’m done with the project file, it looks like this:

I won’t bother running that now (but I’ve done that with other projects before — it’s a useful smoke test). Instead it’s time to pour all the code we wrote in the earlier parts of this series into the ucdump.clj source file. I’ll also fix the namespace definition at the top of the file, and add some comments to the functions:

The main program creates a line for each character in test-str, and prints them to the standard output.

Leiningen knows from the project file’s :main setting that the function to call when starting the program is in the com.coniferproductions.ucdump namespace, so the -main function from there is the one to use.

Time for a test run!

The application can be tested by changing to the project root directory and saying:

then the application will produce same output as above, because my testfile-utf8.txt contains the same text as test-str in the code.

Put in in a JAR

Leiningen has already equipped the project file with the means to make a standalone application. That is done by creating an “uberjar”, which packages up the application and all its dependencies so that it can be run using the Java VM. So if, in the project directory, I say:

lein uberjar

Leiningen responds with:

Compiling com.coniferproductions.ucdump
Created /Users/Jere/Projects/ucdump/clojure/target/uberjar/ucdump-0.1.0-SNAPSHOT.jar
Created /Users/Jere/Projects/ucdump/clojure/target/uberjar/ucdump-0.1.0-SNAPSHOT-standalone.jar

The output is the same as above. However, if you neglect to provide the filename when you run the application, you will get an ugly error message:

Exception in thread "main" java.lang.IllegalArgumentException: No implementation of method: :make-reader of protocol: #'clojure.java.io/IOFactory found for class: nil

and a stack trace, which might make no sense at all. There is no need to add extensive command-line argument handling to the application (if you need that, take a look at the tools.cli library), but it’s good to do a quick check for the missing argument. This requires one little change in the -main function:

If the argument count is not zero, read from the file specified in the first argument; otherwise do nothing.

To make ucdump a proper UNIX-style tool, it should read from standard input if there is no filename. Maybe I’ll update it to do so when I find out how. For the latest version of the source, see the ucdump GitHub repository.

Onwards

This concludes the series. I realise I have perhaps irrevocably managed to combine the words “functional”, “programming” and “stupid”, but the real intent is in the “without feeling” part. I’ve sometimes felt that I would need to be some sort of genius programmer to understand Clojure, and certainly some proponents make Clojure sound so obvious that you can’t help thinking if there’s really something wrong with me. There must be something in the air (and not just Clojure/conj coinciding, which I honestly didn’t know about), since I just found out that Adam Bard had published Clojure is not for geniuses on 18 November 2014, a day after I started this series. That’s parallel evolution at work!

I wanted to tease out some practical aspects of Clojure without theory or condescension, and hope that this series helps you learn a little more about Clojure programming.