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.
I don’t know why, but my Perl programs have always seemed to lend themselves to lengthiness over brevity. That is not the fault of Perl itself. Part of my problem is [re-]learning a new language again for the first time, and keeping all the code in one place for easier debugging, and part of it is I am only just seeing what could be possible in the ability to use both array and hash references.
For example, like variables could be created in a subroutine and returned as part of a hash ref or array ref. Variables can be declared as needed, something I do not remember as part of Perl, when I used it thirteen and ten years ago, respectively.
As I write several new programs for new rate structure and quarterly water billing, the code will get cleaner.
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.
Nearly four years ago, I started learning a new language, Python, because I felt that as a newer language it would make sense to use it as the implementation language for a water endpoint/meter configuration and reads store and forward system. That reason extended to someone needing to maintain the system after I was gone. Using Python alone did not make the application’s modules easier to maintain, but I give some credit to the language for it.
Of all things in Python that are visually difficult for me to comprehend are decorators. I have never used one or did not know I needed to. If I were using Python on a more regular basis, like when I was designing and implementing the system, I would just decide to conquer this subject once and for all. However, one thing I have learned over the years is to be suspicious of overly complicated things that perhaps should not be, and I leave decorators there. I could have missed it, but I was not able to find a description of decorators in Mark Pilgrim’s Dive Into Python.
Perhaps I will find that post out there that just puts it all together for me, but so far, I just see confusion where decorators are concerned.
I try never to give up, so this tutorial is a good start.
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))