Clojure: Language as thought shaper
I recently read an interesting article by Tom Van Cutsem where he describes some of the goals that influence the design of programming languages and one which stood out to me is that of viewing ‘language as a thought shaper’:
Language as thought shaper: to induce a paradigm shift in how one should structure software (changing the "path of least resistance").
To quote Alan Perlis: "a language that doesn't affect the way you think about programming, is not worth knowing." The goal of a thought shaper language is to change the way a programmer thinks about structuring his or her program.
I’ve been rewriting part of the current system that I’m working on in Clojure in my spare time to see how the design would differ and it’s interesting to see that it’s quite different.
The part of the system I’m working on needs to extract a bunch of XML files from ZIP files and then import those into the database.
From a high level the problem can be described as follows:
- Get all files in specified directory
- Find only the ZIP files
- Find the XML files in those ZIP files
- Categorise the XML files depending on whether we can import them
- Add an additional section to good files to allow for easier database indexing
- Import the new version of the files into the database
Clojure encourages a design based around processing lists and this problem seems to fit that paradigm very neatly.
We can make use of the ->> macro to chain together a bunch of functions originally acting on the specified directory to allow us to achieve this.
At the moment this is what the entry point of the code looks like:
(defn parse-directory [dir] (->> (all-files-in dir) (filter #(.. (canonical-path %1) (endsWith ".zip"))) (mapcat (fn [file] (extract file))) (filter (fn [entry] (. (entry :name) (endsWith ".xml")))) (map #(categorise %))))
The design of the Scala code is a bit different even though the language constructs exist to make a similar design possible.
The following are some of the classes involved:
- ImportManager - finds the XML files in the ZIP files, delegates to DocumentMatcher
- DeliveryManager - gets all the ZIP files from specified directory
- DocumentMatcher - checks if XML document matches any validation rules and wraps in appropriate object
- ValidDocument/InvalidDocument - wrap the XML document and upload to database in the case of the former
- ValidationRule - checks if the document can be imported into the system
It was interesting to me that when I read the Scala code the problem appeared quite complicated whereas in Clojure it’s easier to see the outline of what the program does.
I think is because we’re trying to shoe horn a pipes and filters problems into objects which leaves us with a design that feels quite unnatural.
I originally learnt this design style while playing around with F# a couple of years ago and it seems to work reasonably well in most functional languages.