These are three random topics about Clojure that are sort of connected, in that all three address Clojure’s incredible flexibility and genericity from different angles. They were born from a few recent small projects that I started.
My favorite facility for polymorphism in Clojure
Object-oriented programming, at least as presented in C++ and Java, lets you do three things much more easily than languages like C:
- Encapsulation. You can hide data from certain parts of the code.
- Inheritance. You can create hierarchies of related objects which share some behaviors and modify other behaviors to suit themselves.
- Polymorphism. You can implement a function which behaves differently depending on the type of its arguments.
I’m being deliberately vague. Even though these three principles were all over the multiple choice section of the exam on Java when I was in school, they actually transcend object-oriented programming. They’re things which are helpful in all languages. Languages differ in how they support these principles, though.
Encapsulation lets us limit the number of things we have to worry about by only allowing certain parts of the code to modify data. If we have a global variable and we discover in debugging that its value is wrong, the piece of code that made its value wrong could be anywhere. If we have a private member of a class, the piece of code that made its value wrong can only be in that class.
Clojure doesn’t really support encapsulation. Like JavaScript and C, data in Clojure is local or global. You can do weird contortions with closures to make things private, like people do in JavaScript. I wouldn’t, but you can. Since Clojure data is almost all immutable, it’s not nearly as necessary to have encapsulation.
Clojure doesn’t really support inheritance either. But how much of the inheritance we see in Java is about avoiding code duplication, and how much is about the type system? Clojure is dynamically typed, so it doesn’t need inheritance for a type system. And Clojure has macros, the ultimate way of reducing code duplication, so it doesn’t really need inheritance for that reason either. You can build your own inheritance using derive
and the polymorphism tools, but the language doesn’t support it directly, not really.
Clojure supports all kinds of sophisticated mechanisms for polymorphism. Records, types, protocols, multimethods. Probably some more that I’m forgetting or that only exist in the upcoming 1.8.1 alpha pre-release snapshot. All of them are useful and interesting, but my favorite example of Clojure polymorphism is probably tree-seq
.
Why do I love tree-seq
so much? Because this is the only polymorphic function in any language I can think of that both does not care, in any way, in the slightest, not even a little, how you represent the data it works on, and is also so simple that you can make a call to it in one line with practically no boilerplate:
(tree-seq #(not (nil? %)) #(get % 1) [:root [[:a [[:b nil] [:c nil]]] [:d [[:e [:f nil]]] [:g nil]]]])
It works on this horror of nested vectors, producing a lazy sequence of the child vectors of each node. Even though this thing is the most ghetto way I can imagine to represent a binary tree, tree-seq
doesn’t care and still processes it.
It also works on slightly more sophisticated structures:
(defrecord Node [name children]) (def t (->Node :root [(->Node :a [(->Node :b nil) (->Node :c nil)]) (->Node :d [(->Node :e [(->Node :f nil)]) (->Node :g nil)])])) (tree-seq #(not (nil? %)) :children t)
tree-seq
is so cool, you can even do weird things like storing the tree’s structural information in one place and its data in a completely different place:
(def children {:root [:a :d], :a [:b :c], :d [:e :g], :e [:f]}) (tree-seq #(not (nil? (get children % nil))) #(get children % nil) [:root :a :b :c :d :e :f :g])
Sure, this example is weird and pointless. But it works. tree-seq
does not give a hot toddy how your tree is represented, as long as there’s some way to tell which nodes are leaf nodes and some way to get the children of non-leaf nodes.
There’s something refreshing about that freedom. It’s nice to think “For the rest of my code, it would be really nice to represent a tree as XXX” and know that Clojure’s built-in functions can work with that representation. No need to create abstract anonymous protected thread-non-mutable generic reified construction apparati for your new tree representation, or write five hundred lines of XML to register it with your dependency injection framework.
You Can Only Luminus Once Per Project
When I wrote Odyssey Through Three Web Frameworks, I was under the mistaken impression that Luminus was a web framework like Rails. It’s not, and you will be disappointed if you expect it to be.
Facts about Luminus:
- It’s implemented as a Leiningen plugin, and in the Java world it would probably be part of Eclipse.
- It generates a bunch of useful namespaces for you, and includes a bunch of useful libraries for you, and it does give you some stub functions, but it has nothing as extensive as Rails’s ActiveRecord or Django’s admin interface.
- If you don’t like the libraries that Luminus includes for you, it’s easy to pick a different one. Luminus uses Selmer, which is like a Clojure implementation of Django’s template language. I use Enlive. Luminus was okay with that, and we were still able to work together.
- Luminus still does not do security.
- Luminus is Homu Homu’s favorite Clojure technology, even if she can only use it once a day.
In short, Luminus is still just as do-it-yourself as Clojure always was, while helping out with boilerplate and giving just a bit more guidance than you would get without it.
The thought-polluting effects of Clojure
Recently, I wrote some Python code. It’s been a while, and it was surprisingly good to be back, even though I was definitely not loving on LXML.
A few years ago, whenever I started a new program, I would write a class. I would think about how to carve things up into classes. A few times, I wrote C programs, and I felt lost and confused because I couldn’t put things in classes.
I did use standalone functions in C++ and Python, but only when the function was completely unrelated to the rest of the program. If it was some random utility, I would write it as a standalone function. And if I was overloading an operator that C++ requires to be overloaded as a standalone function, I would write it as a standalone function, after first trying to write it as a method, staring at the bizarre compiler errors for five minutes, and then remembering that you can only overload certain operators as standalone functions. But I usually wanted everything in a class, even when I wasn’t really using the facilities of classes for anything. Both versions of PySounds, for example, stick all the logic in a class, then have a main module that instantiates an object of that class. In PySounds2, the main module does other UI-related things as well, but in PySounds1, it was almost literally just the code if __name__ == "__main__": PySounds().runStuff(lexfile, scfile)
.
Then suddenly I ran into Clojure. In Clojure, there are no classes. You write everything as standalone functions and stick it in a namespace. I wouldn’t say I felt lost and confused over this. Unlike C, Clojure has namespaces, so I didn’t feel like I was making program soup. But it did take some getting used to. I proceeded to get used to it.
Now, when I go to write some Python, I don’t jump right to classes. I go as far as I can with standalone functions and modules. If I write some code that feels twisted because of the data access patterns, I’ll consider whether it seems classy or not. If it does, I might write a class. Or I might not; I might just add some keyword arguments to a function. I might just stick two pieces of data in a tuple and pass that in or out. I organize Python like it’s Clojure, with modules as namespaces.
This is still pretty idiomatic in Python, but a while back, when I unconsciously applied the same pattern to Java, things got ugly. Java wants, needs, can’t live without, everything in classes. I wanted things not to be in classes. I wrote a class with a bunch of static methods. When I put it up on Code Review.SE, someone complained that it wasn’t object-oriented. And that was just, because it wasn’t, because I didn’t want it to be and Java did and we fought and ended up at a lousy compromise, as usually happens when you disagree with Java.
I’m not sure if there’s really any significance to this change in approach. I’ve felt for at least the past couple years that object-oriented programming was a lot more heavy machinery than I needed for most of the programs I work on. Especially in Java, when you start getting into generics and things get crazy-complicated.
I kind of appreciate Martin Odersky’s approach to Scala, where he knowingly cordoned off all the crazy type rules and made them library implementors’ problem, both at the language level and culturally in the Scala community. Actually, I appreciate a lot of things Odersky has said and done, which is why I have kind of a soft spot for Scala even though the language, on first inspection, doesn’t really match my aesthetic taste. I would like to spend some more time with Scala someday. That might revive my interest in object-oriented programming a bit. Right now it’s at an all-time low.
On the other hand, I recently went over some posts on Code Review.SE where the posters seemed to have totally ignored Clojure’s rudimentary object-oriented features, either knowingly or from ignorance, even though those features were perfect for their problem domain. (Both games, as I remember.) I realized that I really don’t know these features that well, so I got started on a simulation program where I would use them. I haven’t gotten far yet, but I’ve been quite pleased with records and protocols so far. They seem to be simple, flexible, and performant, while also being more sophisticated than regular maps or C structs.
Conclusion
I guess the only takeaway here is that Clojure has had a positive effect in code I write in other languages, by pushing me to make things more polymorphic and generic, and by spurring me to resist needless complexity. Paradoxically, by refusing to be object-oriented, Clojure has brought me greater understanding of object-oriented programming, both its upsides and its downsides. Now, instead of mindlessly classifying everything, I see how far I can go without classes, and when some particular code seems like it could really use a class, I use one. Rather than heavyweight classes with fifty member variables and hundreds of methods, I prefer lightweight classes with a single obvious purpose.
It’s tempting when you first learn about OOP to use it all the time, for everything, especially when your professors and your languages are expecting it. But it’s good to find out how things would go if you didn’t have OOP, whether you’re using C, JavaScript, or Clojure.