Foxes and Hedgehogs

When I first started programming, which wasn’t actually that long ago, but it feels like ages because of the constant churn, languages were like drunks in bars: they all had a philosophy. Some of them made a lot of sense, like Ruby’s “Principle of Least Surprise”. Some of them were incoherent, like Perl’s “TIMTOWTDI” and “GALFKGAFCVHAJRTOAIADGDLFJKC”. Many were encapsulated in nice little slogans that people could yell at each other in forums. Others were implicit, like Java’s philosophy, “Make Insanely Complicated Libraries So Companies Have To Hire Exorbitantly Priced Consultants To Teach Their Programmers To Accomplish Basic Tasks In Java”. But whether the philosophy made sense or didn’t, whether the philosophy was implicit or explicit, the languages all had one.

The philosophies served a lot of social purposes, but in most cases they did begin as a motto or design principle that the language creators kept in mind when designing the language. Recently, the languages I’ve seen becoming popular, like Kotlin and Swift, don’t seem to have philosophies. They have purposes: Swift’s purpose is freeing iOS and macOS programmers from having to manually decrement reference counts, and Kotlin’s purpose is to integrate with Jetbrains IDEs. But they don’t seem to have language design philosophies the way older languages do.

A lack of a language design philosophy is sort of a language design philosophy. It means the creators don’t have an overriding vision for the language. But without an overriding vision, how do they decide which features get in and which don’t? I don’t know a ton about either Kotlin or Swift, but one thing always stuck out to me in tutorials on them: the tutorials read a lot like lists of features from other languages. “Swift has for-each loops. Swift has lambdas. Swift has classes. Swift has limited type inference.” They would explain how the particular implementation worked, but it was usually substantially similar to other well-known languages. Kotlin and Swift are not random assemblages of features the way languages like C++ and Perl 5 and PHP are. They have an aesthetic, which I’d call roughly “functional-lite”. They’ve settled on a Python-esque profile, largely imperative with object-oriented and functional features worked in around the edges, and added some Java-style static typing. I divined that they choose features based on what they think working programmers will find useful.

Of course, no popular language aimed at working programmers purposely adds features that are highly likely to be useless. But some languages are built according to a strong, overriding vision of what set of abstractions will ultimately prove useful to working programmers. Others are more pragmatic; they add whatever features have been proven, through long use in other languages or through long experience without the feature, to be practical and useful for working programmers. Older languages made this into a philosophy itself: Perl’s “There Is More Than One Way To Do It” was expressing a belief that there should be multiple syntaxes for the same abstraction, presumably because each one would feel natural in different situations. (I never worked with Perl, but the concept does make sense—the same abstraction can manifest concretely in different ways, each of which feel appropriate in different situations.) This distinction is sort of a meta philosophy on top of the various philosophies (explicit and implicit) of different languages. Swift and Kotlin are in the latter camp, but since they’re backed by large corporations and have special niches where their dominance is uncontested, they didn’t need to create marketing slogans that explicitly state it the way Perl did. They didn’t need to differentiate themselves from other languages with marketing; they differentiated themselves by being the language of Apple / Jetbrains and by being the language of iOS development / extreme IDE integration.

Once I realized this divide existed, it reminded me of foxes vs. hedgehogs. The idea comes from a snippet of Ancient Greek poetry attributed to Archilochus: “The fox knows many things, but the hedgehog knows one big thing.” To wit, the fox stays alive with many tricks to keep ahead of its enemies, whereas the hedgehog has one big trick, stabbing its enemies on its spines. (We’re excluding the possibility of a hedgehog that’s the fastest thing alive.) I first heard about it in politics. Some politicians dabble in many issues; they might have a few flagship issues, but they’re conversant with almost all of them, and they’re quick to educate themselves about new issues. Other politicians have one big issue that they care about more than anything, and are less concerned with other issues. Isaiah Berlin applied it to writers in his essay The Fox and the Hedgehog: some writers, like Plato, Dante, and Nietzsche have one big idea that colors all their work, while others, like Shakespeare, Joyce, and Goethe, write about a wide range of ideas and experiences. It’s the same with programming languages. Some languages are fox languages: they might have a few flagship features, but they’ll include various features from all over the place, and while they’re not necessarily garbage heaps of trendy features, they’re more open to borrowing. Some languages are hedgehog languages: they’re built around a singular vision. The vision might be towards a stance on a big philosophical issue. It might be a vision of a language built around a big abstraction. It might be a vision of a language perfectly suited for a single restricted domain. For a hedgehog language, everything that goes into the language serves its one singular vision.

Why is this distinction useful? Because new languages (and libraries, frameworks, platforms, etc., which can be judged much like programming languages) appear constantly, and they’ll all tell you that they can solve all your problems. Evaluating whether the tool’s meta-philosophy matches your goals is a good first step to finding out if that’s true. It’s not about one or the other being bad or wrong. Both have their uses.

If you think hedgehog languages are useless, think about this. Nobody likes writing multithreaded code. Well, okay, some people like writing it, but nobody likes debugging the insane synchronization issues that crop up later. Nobody likes spending a bunch of time crafting the perfect multithreaded architecture only to discover it has a fundamental flaw that makes all your code actually just really expensive sequential code. So a bunch of hedgehogs who had a vision of a better way to do this concurrency thing went off and created abstractions like software transactional memory, actors, communicating sequential processes, single-threaded event-driven programming, and others, and then a bunch more hedgehogs made languages like Clojure, Erlang, Scala, Go, and Node that baked in support for them as part of their visions. Another big victory for hedgehogs: OOP. Hedgehog languages like Smalltalk and Java went all in on OOP and convinced a generation of programmers that writing libraries in C was painful and bad. Whether you think OOP was a good idea or not (I’m starting to have my doubts nowadays), it was a huge propaganda victory for hedgehogs.

I hope it takes less convincing to see that fox languages have their uses. Their benefits are more obvious: programmers who’ve used the same features in other languages can jump right in and understand them without learning new abstractions. Knowledge about how to solve problems in other languages carries over without any trouble. The thing is, these seem to be cyclic. For a while, everyone just uses the fox languages and appreciates all their homey conformity and routine. Want to write a website? Here’s a fox language. Want to write a mobile app? Here’s another fox language that’s basically the same, except maybe you can leave parentheses off no-arg function calls. Want to program an Arduino? Here’s yet another fox language that’s basically the same as the first two, except maybe it has an easier dictionary syntax. Then some new problem appears. Reusable software. Distributed computation. Big data. Machine learning. Whatever. The old fox languages are missing something that makes this new problem easy to solve. So a round of hedgehog languages appears, built around a single grand vision of how to solve this new problem. Sometimes people adopt one of these hedgehog languages at large scale. Sometimes it survives multiple cycles by turning into a fox language. Other times, the new hedgehog languages are too inaccessible or don’t have enough marketing or whatever, but after a while a new round of fox languages appears that brings their features to the masses.

This is where I see Swift and Kotlin fitting in. Everyone was aware that Java 6 was making things harder. Everyone knew Java 6 didn’t have the right abstractions for modern, quick-churn web and mobile code. A round of hedgehog languages came out to address this—-Scala, Clojure, Go. They’ve gotten some adoption. Go is foxy enough, and backed by a huge enough company, that it might live to become a fox language. But the barrier to entry was high enough, and the lack of marketing profound enough, that most of these languages haven’t caught on in a big way. Not like Java, or Python, or PHP have. So now we’re having a round of fox languages—-Swift, Kotlin, Javascript after ES2016—-that consolidate features from all of them. We’re having languages like Java go foxy and add features from these languages to new versions.

So at this point, you might be saying “That’s great, but I’m a programmer who writes boring business apps. How do I figure out what I should be using?” I’m glad you asked! The distinction between fox languages and hedgehog languages is most important of all for you. Boring business apps have a particular set of constraints that are different from most other kinds of program. The code will be changed in a million places in a million small ways over its lifetime. A wide array of programmers should be able to understand the code, and legibility, documentation, and easy modification are the most important characteristics. There is almost never a good reason to use hedgehog languages for boring business applications. Hedgehog languages are based on a grand vision of how computation should be expressed. Boring business applications are not about grand visions of how computation should be expressed. Boring business applications are best expressed in the abstraction of structured programming, which is now such a fundamental part of programming languages that it doesn’t even matter whether a language is for foxes or hedgehogs, it does support structured programming. So, if you write boring business applications and you’re evaluating a language (or framework, database, etc.), you should look for signs of foxiness. Are the features widely supported in other, similar, existing tools? Do the creators stress practicality, usability, reliability? Are the overarching abstractions familiar, or at least widely available in other foxy tools? If you’re seeing a bunch of unfamiliar features, or familiar features mixed in unfamiliar ways; if the creators are talking about vision, about changing the way things are and blowing up the status quo and revolutions and paradigm shifts; or if the tool is based around abstractions that you’ve never seen before or only exist in very obscure tools, then you’ve got a hedgehog tool. It’s much harder to evaluate whether a hedgehog language is suited to what you need. Hedgehogs have to be carefully matched to a use case and a set of problems, because hedgehogs are usually created in response to a specific use case and set of problems.

I’ll end by listing off a few languages and evaluating whether I consider them fox or hedgehog, as they currently stand.

C: At the time it was made, very much a fox language. Now it’s something of a hedgehog language, in that its one big use case is programming in low-level, resource constrained, or high performance contexts. You probably wouldn’t choose to write your startup’s killer new social network in C.

C++: Definitely a fox language. C++ keeps on adopting new features like lambdas that proved popular in other languages. It’s mostly seen nowadays in similar contexts as C, but unlike C, it keeps piling on new features and new abstractions in response to programmer demand.

Java: Java started as something of a hedgehog language. It’s become less of one over time, but still looks spiny enough to me. It was object oriented programming for the masses, C++ without the rough edges, Smalltalk without the flexibility, and its adherence to object orientation still seems a bit militant. It’s gotten new features like generics and lambdas, but it’s always added them by putting a thin layer of syntax on top of an existing part of the language: generics just tell to compiler to automatically insert a cast for runtime, and lambdas are just syntactic sugar for anonymous classes implementing interfaces with one method.

Python: Python is a fox language with a few hedgehoggy principles, like “There should be one and only one obvious way to do it”, which leads to enforced whitespace conventions. Over time, it added features like classes, map and reduce, list comprehensions, iterators, generators, and async / await in response to other languages. It also made it relatively easy to call code written in C, which is the foxiest trick of all.

Python is also a good case study on communities vs. languages themselves. The Python language is a fox, but the community behaves like a bunch of hedgehogs. Python seems to usually resist adding any feature until people have been asking for it for three or four releases, and then someone will make a PEP and it’ll get added.

Ruby and Perl: These two are a lot like Python, but with even fewer overt hedgehog tendencies. They both borrowed things from bash, which is a very foxy language in that you only use when you have no other way to accomplish something.

Scala and Clojure: These two are the opposite of Python, Ruby, and Perl, which were foxy with a few hedghoggy traits. Scala and Clojure are hardcore hedgehogs with a few foxy traits, the biggest one being that they run on the JVM and can interoperate with Java code. Scala has a grand vision of mixing Java-style OOP with Haskell-style functional programming. Clojure has a majestic ambition to be a Lisp dialect for the modern era, mixing a solid concurrency story and good support for hash maps with the functional features of Haskell and the homoiconic power that Lisp is known for. Both of them have high learning curves due to lots of unfamiliar abstractions and unusual features, like Scala’s implicits and Clojure’s menagerie of types for managing state under shared memory concurrency.

Lisp in general is a hedgehog language. It has a grand vision of all programming as lists of lists that directly represent an abstract syntax tree. Common Lisp is a Lisp that tried to be foxy with stuff like rplaca and the progn macro. Scheme is a Lisp that skewed further towards the hedgehog. Clojure is somewhere in the middle; it runs on the JVM and adds features like hash maps, sets, and regex literals that are foxy compared to Scheme, but also encourages pure functional programming and immutability to an uncomfortable hedgehoggy extent. Its concurrency stuff is pure hedgehog—yes, even core.async. We’ll get into this in the next section.

Go: I’ve only used Go a little bit, but to my eyes it’s a language that lures you in by appearing to be a fox, but then when you go to pet it, surprise! You’ve got hedgehog spines stuck in your hands! It’s a hedgehog that knows how to play tricks like a fox.

There are two aspects to Go’s inner hedgehog. One is its concurrency stuff, with goroutines. Yes, I know it’s based on communicating sequential processes, from the 70’s. The thing is that there are at least two whole generations of programmers who’ve grown up never hearing about communicating sequential processes and never using a language that supported it. There’s the 1990s era of big enterprise Java developers, and there’s the current era of hipster startup Node hackers, plus a few intervening generations, all united in their never using a language that supported CSP as a first class abstraction. Since Clojure’s core.async is a ripoff of goroutines (as admitted by Rich Hickey himself), it’s also a hedgehog trait.

The other aspect is Go’s slavish adherence to C’s precedent. It has pointers. It has printf and scanf. It doesn’t have generics, and has repeatedly refused requests to add them, and justified this decision by appeals to Go’s design philosophy of simplicity. Whenever a widely requested feature is refused because of a design philosophy, that’s a sure sign of a hedgehog. I’m not saying whether that’s right or wrong, but it is a fact. C itself was foxy, but Go’s insistence on copying it beyond reason is total hedgehog.

My Biases

To disclose my own biases, I actually like hedgehogs, except when they go wrong, which they can, terribly, much more easily than foxes. Go’s hedgehog nature was what induced me to try it over something more foxy like Node or Kotlin. Clojure’s hedgehog nature induced me to try it over something more foxy like Scala. (Yes, Scala is also a hedgehog, but it looked deceptively like a fox from far away.)

Hedgehog languages are usually the ones that win over the people, but don’t get any traction in industry, and forums are filled with programmers wondering why no one can see how great this language is and what a killer secret weapon it would be for some team.

Even though I like hedgehogs, when the chips are down and the title is on the line, I always go back to foxes. If I have to program something and I have no idea what I’m doing, it’s back to Python. I don’t reach for Clojure or Go, because with hedgehog languages, you never know when you’ll run into some problem that, due to its very nature, doesn’t translate well into that language’s idioms and abstractions. If it turns out that a bunch of hideous index manipulation is the only way to solve the problem, I’d rather take my chances with Python than write weird Clojure code full of atoms and swap! and reset!.

Java is a hedgehog language. And a lot of pain in modern software has happened because people used Java somewhere where its insistence on object-oriented programming, combined with the militant adherence to object-oriented design principles that its programmers have developed, didn’t do a good job representing the problem domain. I work on a Java codebase at my job, and every day, I run into things that Java makes harder by being such a freaking OOP hedgehog. Testing, for example. You can write testable code in Java, but features like private access and lack of standalone functions are hostile to it, not to mention all the ways Java developers’ culture has evolved to make it harder to write testable code. This has led to tons of cultural conventions and weird libraries to make writing testable code possible, but still not easy. If the chips were down and the title was on the line, I would never turn to Java. It’s fairly likely if I pick Clojure that I’ll have to do some sequence manipulation that’s hard to translate into point-free applications of map, reduce, and filter, but it’s just as likely if I pick Java that I’ll end up wanting arbitrarily nested maps, or pairs, and have to run off and write new classes to represent them. And then I might want a 3-tuple. Guess what? Another new class!

On the other hand, when you have to explore a gigantic monolithic codebase written by 50 programmers over five years, Java’s not so bad. It forced the other programmers to explicitly write out everything, explicitly represent everything with classes and statically typed variables, and even tag which methods you need to worry about seeing again outside of that class. It’s all about knowing when to pick the right tool. Evaluating foxiness vs. hedgehoggosity is one way to get a feel for a tool. Then you can decide whether that particular hedgehog’s spines will solve your problem, or if you need the cunning of a fox to get through.

Advertisements

Three Haiku on Clojure

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:

  1. Encapsulation. You can hide data from certain parts of the code.
  2. Inheritance. You can create hierarchies of related objects which share some behaviors and modify other behaviors to suit themselves.
  3. 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:

  1. It’s implemented as a Leiningen plugin, and in the Java world it would probably be part of Eclipse.
  2. 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.
  3. 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.
  4. Luminus still does not do security.
  5. 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.

I like Python! No, wait, Ruby! No, but Python…

I hate Ruby.

There I was, just buzzing along, using Python, satisfied that I knew one of the coolest languages around, and definitely the coolest language you could actually get a job with.

Then Ruby came along.

It was English-like. It was practical. It was fun. It was possible to get a job with it. It was even Japanese.

Almost everything about Ruby’s syntax, language features, and library just made sense to me almost immediately. I don’t mean I immediately understood the entire language—blocks took me quite a while to understand, and metaprogramming can be pretty confusing for almost anyone, unless you’re Dan Friedman. I mean that I almost immediately saw, on some intuitive level, why Ruby had been designed the way it was. My mind easily fell into thinking the way Ruby wanted. Even when I barely knew the language, I could figure out just where to bust out a line-ending if or while. I immediately loved string interpolation.

By contrast, there are plenty of things in the Python language and libraries that, to this day, do not make sense to me. I learn them by memorization and use them. Examples are len being a function instead of a method, and the entire os.path module. Once, I needed a quick script to process a few files in some nested directories. I’d only been learning Ruby for about three days, and I wanted it done quickly, so I used Python. I struggled for an hour to write a Python program using os.path.walk, and finally decided to look into how Ruby would do it. I had the thing done in Ruby in ten minutes.

Other parts of Python make sense in a tortured sort of way, like writing ' '.join([1, 2, 3]) instead of, say, join([1, 2, 3], ' ') to join some strings. Yeah, fine, I understand that it makes some kind of sense to think of it as “String ‘ ‘! Join the list [1, 2, 3]!” rather than “List [1, 2, 3]! Join yourself using the string ‘ ‘!”. Just like it makes some kind of sense that C++ uses virtual void flagellate(int flagellant) = 0; to declare a pure virtual function, since you’re initializing the function’s pointer to null. But just because they kind of make sense when you think really hard about them, that doesn’t make them obvious (like the Zen of Python claims to want), and definitely doesn’t justify the kind of condescending blather that the Python FAQ presents in response to the question of why it’s ' '.join([1, 2, 3]).

Maybe it’s because I started as a natural language guy, in English and linguistics, that Ruby makes so much sense to me. People complain that it has too many different ways of accomplishing the same thing. It definitely flies in the face of Python’s “There should be one obvious way to do it”. On the other hand, Python itself frequently chooses ways to do things which aren’t incredibly obvious, like the aforementioned os.path.walk. That there is only one way to do it, if anything, just makes the non-obvious nature of that way hurt even more, because there’s no way to get out of it.

To me, Python is designed like a formal system, while Ruby’s design is more like a natural language. Of course, there are languages much more like formal systems than Python; there’s Lisp, and Haskell, and Scala. Even Java, once you stop looking at syntax and warts like arrays and primitive types, and just look at its efforts to be “objects all the way down”. But Python seems to have aspired to the same kind of minimalism as Lisp. Python aspires to rid itself of synonyms and homonyms, to make all antonyms expressible by sticking “not” at the front. Ruby takes on extra syntactic baggage like “unless” and suffers methods with different names that do the same thing, in the name of being more obvious and readable to humans. Python believes that explicit is better than implicit. Sometimes you have to write more for the sake of making it explicit. Ruby tolerates weirdnesses like the flip-flop operator, which does a boatload of implicit stuff behind the scenes, on the principle that it’s fairly common to do something like that, so you can memorize one set of implicit behavior.

Please don’t take this to mean I think Ruby is better than Python. I said that Ruby, the language, and several of the libraries, fit my style of thinking better than Python. And even more, please don’t take this to mean that I think crazy things like Perl’s implicit $_ variable are okay. Ruby, I think, gets just the right amount of redundancy and implicit behavior; if Python has too little, then Perl has far, far too much. Ruby, I think, gets just the right distance between natural language and formal system; Scheme and Haskell are a little more like a formal system than I prefer, but Cucumber and AppleScript get much closer to natural language than they should. Cucumber and AppleScript are wonderfully easy to read, but horrible to write, while Scheme and Haskell are nice to write, and the end product is very aesthetically pleasing in a classical sort of way, but can be rather hard to read.

So, given that I liked the Ruby language better than the Python language, why haven’t I dumped Python yet? Third-party libraries.

Ruby has amazing libraries related to the web. Rails has been extensively talked up. I made another visit to it after I put some serious effort into learning Ruby, and I found it a lot more pleasant than I remembered. By contrast, I started to find Django code-y and somewhat laborious. Sinatra is also well-liked. Nokogiri blows away any HTML/XML processing library anywhere that I can think of; Python’s Beautiful Soup is great for scraping HTML, but less useful for XML, while Clojure’s Enlive is great for scraping and templating, but again, less useful for XML than Nokogiri. Let’s not even talk about Python’s standard library HTML and XML parsers; the HTML parser is a horrible Java-wannabe library that makes you implement your own API, while the XML parsers are cumbersome and limited.

Ruby’s libraries in other areas that are of interest to me are less impressive. Python has NumPy, SciPy, Natural Language Toolkit, BioPy. Clojure puts in an appearance with Incanter and OpenNLP, and there are some Java libraries here that you can use. Ruby has basically nothing in scientific computing, NLP, or bioinformatics. It could be great for all of these areas, because they’re all interdisciplinary, and I suspect that people from outside math and computer science would find Ruby easier to just get than other languages, the same way I did. But the Ruby community’s main concern is the web, and it shows.

If Python were a bad enough language, I wouldn’t feel so conflicted. But Python’s not a bad language; it’s a great language with a few annoying facets, most of them superficial. I like Ruby the language better, but Python’s sure better than a cup of hot Java in the face, to say nothing of being skewered by a C++. Most of the libraries I want are in Python. Some are in Ruby, but the ones that aren’t are huge ones that aren’t likely to be replicated in Ruby any time soon. Python is, in fact, probably the only language that has all the libraries you could ever want, is pretty nice to read and write, and can also get you a job. And I would have been happy with it forever, if that stupid Ruby hadn’t come along.

But I’m also mad at Ruby for creating this internal conflict for another reason: if I had never learned of its existence and just committed to Python, maybe I would know it well enough to have a job by now.

Stupid Ruby.