I'm writing a little tool for mangling MP3 collections and, as a challenge, I decided to write in in Hy, a dialect of Python that uses Lisp syntax. Like every good developer, I wrote out my list of policies that I want the tool to obey: "If the user doesn't specify a genre on the command line, use the most popular genre found in all ID3 tags in this directory," "If the user doesn't specify an album name and the most popular album is an empty string, try to derive the album name from the directory."

When I was writing that, I realized that the filter for each file's fields could be expressed as an anonymous function over the collection that contains the knowledge on what to do on a per-file basis, based upon the various policies:

(defn make-artist-deriver [opts found likely]
  (cond [(.has_key opts "artist") (fn [tag file] (get opts "artist"))]
        [(.has_key opts "usedir") (fn [tag file] found)]
        [true (fn [tag file] (or tag (sfix found) (sfix likely)))]))

(defn make-album-deriver [opts found likely]
  (cond [(.has_key opts "album") (fn [tag file] (get opts "album"))]
        [(.has_key opts "usedir") (fn [tag file] found)]
        [true (fn [tag file] (or (ascii-or-nothing likely) (sfix found)))]))

(defn make-genre-deriver [opts found likely]
  (cond [(.has_key opts "genre") (fn [tag file] (get opts "genre"))]
        [true (fn [tag file] likely)]))

(defn make-title-deriver [opts found likely]
  (cond [(.has_key opts "usefilename") (fn [tag file] (title-strategy file))]
        [true (fn [tag file] (or tag (title-strategy file)))]))

This seems like the right way to do things, with each function having the same type signature, and each generated function having the same type signature, so that at the end all I do is run the generated functions through a map-of-files of a map-of-fields of the entries. The user can see the various policies, and it works.

I'm new to Lisp, and I've never seen an example of what I'm doing here. Not in Clojure, or Steel Bank, or Racket, and I have been reading a lot of Lisp in the past couple of months. I can see what I'm doing throughout the code, but I'm afraid that my code might be idiosyncratic and strongly influenced by closure-heavy languages like JavaScript, which is what I write professionally.

Is what I've written idiomatically correct? Am I writing maintainable code?

The whole example can be found at here.

  • \$\begingroup\$ Well... different Lisps are different languages. Coming from Common Lisp / Emacs Lisp background I tend to see closures as an uncomfortable substitute for objects when it comes to capturing the state of a program. But you seem not to be using closures. Instead those are anonymous functions (the difference is that closures capture some of their variables). I won't comment on maintainability since I don't know Hy. \$\endgroup\$ – wvxvw Dec 24 '14 at 21:53
  • \$\begingroup\$ These are using closures; they're capturing the variables presented in the selecting lambdas, which reflect an outer state. \$\endgroup\$ – Elf Sternberg Dec 30 '14 at 0:00
  • \$\begingroup\$ Ah, right, I just looked at the last pair and assumed they are all the same. \$\endgroup\$ – wvxvw Dec 30 '14 at 6:39
  • \$\begingroup\$ That's why I asked the question. This block takes the user's desired strategy as arguments and reifies it into concrete transformation functions with presets-via-closure that are then run against the database. This appears to be a completely valid, but somehow rarely used, technique. \$\endgroup\$ – Elf Sternberg Dec 30 '14 at 21:15
  • \$\begingroup\$ Well, a typical argument against closures is that they make code optimization more difficult (because they allow more variations at call site), and that they are subsequently less efficient to compile (objects, when encapsulate the state tell exactly what they capture, while with closures this is automatic). Besides, they are less universal: suppose in the future you wanted the result returned from make-title-deriver to have several methods - you'd have to give up it being a closure. \$\endgroup\$ – wvxvw Dec 30 '14 at 22:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.