This post is not intended to be a contest of one language versus another. We are using Perl for a water project for a number of pragmatic reasons, like folks working on the water project who do not know Clojure and Perl’s DBI support to name two.
Instead, this post is about something I’ve noticed, having used Clojure on a few small projects, and now returning, like Kellog’s Corn Flakes, to Perl again for the first time (since 2000, and then again in 2003).
I am re-writing three major Informix 4GL programs in Perl. They rely heavily on Perl’s DBI, and hence need to store a lot of intermediate data. So, there are quite a few module and subroutine scope variables. For me, the striking difference between Perl (and perhaps languages like it, including 4GL, VB, and so on) and Clojure is my Clojure programs don’t seem to need variables, other than global vars and data bound in let statements.
I believe my appreciation for Clojure’s immutable data is as full as it can be for someone who has worked with the language for a couple of years, so I appreciate that you cannot initialize a variable and modify it. The design of my Clojure programs always was different, despite the fact I could have bound a lot of let variables wherever I needed them, but I just never needed to do that.
The data seemed to come and go. My Clojure programs read in data; manipulate that data, make network I/O calls using that data; and then write some of that input data and new data out to disk.
When I first started learning Clojure, the luminaries said my views of designing would change, and I believe they have.
Over the past two weeks as I have prepared for a nice long rest, I wound up mostly cleaning up BASH scripts and their Informix 4GL counterpart programs. The changes were made to simplify sending data to and receiving results back from our Registry of Motor Vehicles. While not as interesting or as flashy as working with Clojure, the changes were well needed, because they involved ease of use, reliability, and simplification. To me, it is just fine to have a good cleanup effort, even if it is for an over twenty-five year old application.
Finally, I was able to spend a little time finishing up enhancements to a Clojure program that splits up real-estate assessment data, and this got me thinking.
Working with Clojure is not a good thing just because it is a Lisp dialect and programming in Lisp has always been one of my goals.
Working with Clojure is not a good thing, because it is a functional programming language or even because it is a JVM language. As it turns out the only serious Java I encountered was a Visual Cafe application that was the Vinca/Legato/EMC Co-Standby For Windows configuration application.
Working with Clojure is not a good thing, because it is an up and coming language, though I admit that is part of what drew me to learn it.
And working with Clojure is not a good thing, because using the languages encourages writing small discernible functions, C and shell scripts on Unix encouraged writing small, well written C programs years ago.
Working with Clojure is a not good thing specifically for any of those reasons , but is a great thing, because it is possible to make a change in one area and not break something due to an accidental side-effect that could more easily occur in a language that supports mutable data structures.
This reason is not glamorous or flashy, but sometimes not having things blow up and go to pieces is better than temporary flash and glitz.
Instead Clojure can be a great tool for a one-person band, because it isn’t flashy and its constructs allow for reliability, when there is not a lot of time to ensure safety or reliability.
At the time I started learning Clojure — almost two years ago — some Clojure books existed, like Programming Clojure, Practical Clojure, and The Joy of Clojure. O’Reilly’s Clojure Programming by Chas Emeric, Brian Karper, and Christopher Grand was still being written. Of the earliest books, I like Joy of Clojure the best, because, although it does not cover a wide swath of material, it is very insightful.
In that time, I have written three medium-sized data transformation projects in Clojure, and now realize it is time to go apply more sophisticated modalities like synchronization, despite the fact that most of my work consists of very linear process step execution.
I’ve started re-reading Clojure Programming and discovered something quite nice about it. It serves quite well not only as a teaching source, but as dictionary, topic lookup, source, as well. The topics are fairly well covered, and seem as fresh today as they were when the book came out.
I am grateful when I get a chance to have a a moment of productive programming; wonder when the boom is going to be lowered whenever a period of more than a day of productive programming comes my way; and check my pulse if there is more than a week of productive programming. That has happened recently by needing to rework a Clojure program.
Recently, my Clojure and Python programming has been taking a decidedly more functional turn. That is write code that relies less on constructs from C or Java. In my case this was moving from database row positional dependence to referencing data by column name.
My work deals in those universal data transfer standards .csv and xml. The re-worked Clojure program in question is an application that compares two insurance reports. The information is in .csv format, and each report comes complete with its own first row of column headers.
My original approach to this problem was positional, and after using clojure-csv to create a sequence of vectors, extracted data from each report row using nth. and column indexes defined as defs.
The code looked confusing, was hard to follow, and so I decided to create a sequence of maps using the saved column headers, transformed into map keys.
(defn gen-map-keys "Takes a sequence, and turns it into viable keys for a map. We opt to change spaces ' ' to dashes '-'." [in-seq] (map (fn [element] (keyword (cstr/replace element " " "-"))) (map #(cstr/trim %1) in-seq)))
Then each column header is zipped with a .csv row:
(defn gen-mapped-sos "Takes a sequence, from a sequence of sequences, and column keys, zipmaps them, and returns the map. The column keys must be sanitized, and already run through the key making process, before being submitted." [col-keys row] (zipmap col-keys row))
By doing things this way, functions became more generic, and easier to understand. This might have been due to the fact data comparison was taking place by map key, reworking the software, or a little of both, but the results were good.
Clojure, Python, and other modern programming languages lend themselves to the kind of code in which functions are called in all kinds of places, passed as parameters, returned from functions, and so on. In those instances, handling exceptions becomes tricky. It is not like the good old days of C/C++ coding, or at least today’s software seems less like those days.
Recently, while re-writing some Clojure code that depended on data extraction based on nth into functions that processed sequences of maps, I looked at the vulnerability of the new functions. They assumed the right data was being passed to them. I thought, well what if someone passes a sequences of vectors or lists?
The following functions show an example of what I needed and its solution. There is undoubtedly a better way to have done this, but at least this stops me dead in my tracks, so I can go fix a problem more easily.
(defn seq-of-maps? "Tests for a sequence of maps, and throws a custom exception if not." [s-o-m] (if-not (seq? s-o-m) (throw (IllegalArgumentException. "s-o-m is not a sequence")) (if-not (map? (first s-o-m)) (throw (IllegalArgumentException. "s-o-m is not a sequence of maps.")) true))) (defn ret-map-col "Selects a key from a sequence of maps using a map key." [in-key in-seq] (if (seq-of-maps? in-seq) (map (fn [x] (in-key x)) in-seq) nil))
I am ready to work on my final assignment for the Clojure Course. Overall, I give CodeLesson a thumbs up, but I will have comments on things that could be improved, should they desire them. In other words, if there is a survey, I will be filling it out.
Here are the highlights of what I’ve learned, and it’s not sequences, recursion, emulating objects, inter-operating with Java, or other technical concepts. Instead it is a new way to look at testing — it is just too easy to write tests not to — and ways of creating different layers of code logic, from which DSLs are created. Oh yes, and that means looking at where macros could be used.
I will take additional time to read the macro and functional programming chapters of Amit Rathore’s Introduction to Clojure, because there are some well written areas — the book very good overall — but some of the most complicated areas are just downright elegant. I will continue to look for Clojure articles, because I have found that even sources like the older edition of Stuart Halloway’s Programming Clojure has good pedagogy for particular topics. Blogs like Alex Miller’s Pure Danger Tech have downright great tutorials, and I turned to some of these during the course, just so I could wrap my head around new concepts.
Finally, I need to start writing code and not worry where I am on the Clojure experience ladder. Instead, I will write tests, look for logical separation, and, no matter how simple the task, my experience tells me, you have to write code in and struggle with a computer programming language to achieve mastery in it.
I have purchased either in e-book or printed form most of the major books on Clojure, including both from Manning Publications, Apress, O’Reilly and The Pragmatic Bookshelf . All these books are well written; all have slightly different approaches and styles.
Some are tutorial in nature, others are more like a tutorial/reference, and others contain philosophy in addition to tutorial. In some of these references, there is a sense you have programmed in Java previously, and I sense, and it is not directly implied, that the reader has dabbled in a Lisp dialect.
My Ah Hah! moment came recently when I realized that until a couple of years ago, the last languages I used in the commercial world were C++, Java 2, and Perl. Functional Programming (FP) was an academic subject, and though I had heard of Python, there was no pressing reason for me to learn it, at least back then. These books are written by authors with mostly a different programming learning and implementation environments.
So, as I work my way through Clojure In Action and Code Lesson’s Intro to Clojure, there are additional things for me to learn, the biggest of these the differing implementation environments and curriculum from when I started programming.
Here we go, then.