Clojonic: Pythonic Clojure

In June 2012, I promised myself that I’d learn Clojure “as a mind expander”. As a long-time Python programmer who has been using Python full-time in my work at Parse.ly, I wanted to explore. I wrote then:

I don’t know whether Clojure programs will be better or worse than equivalent Python programs. But I know they will be different.

It took me awhile, but in January of this year, I started teaching myself the language.

Rich Hickey, and the “Cult of Personality”

My approach was to first learn the underpinnings of the language from books and online videos. If you embark on this for Clojure, you will inevitably run into the copious publicly-available material from the language’s creator, Rich Hickey.
In stark contrast to Guido van Rossum in the Python community, Rich Hickey is undeniably not just the Clojure language’s creator, but also a kind of spokesperson for a functional programming renaissance. Guido van Rossum generally lays low and lets the Python language and community speak for itself, and tries to avoid controversy. To him, Python is just a popular tool he happened to create, and it doesn’t represent any major paradigm shift in programming. It’s a positive evolutionary improvement supported by a great open source ecosystem and community. To Hickey, however, “traditional” programming languages — but especially popular ones with an object-oriented focus, such as Java and C++ — are just plain wrong. He proposes Clojure as an antidote of sorts.

You can get the gist of this from his motivating videos, such as Hammock-Driven Development, Are We There Yet?, and Simple Made Easy. For a thorough overview of Clojure as a language, you can also get a walkthrough by Hickey, given to a room full of Java developers, in Clojure for Java Programmers Part I and Part II.

Here is a summary of the viewpoint. Most languages are missing some important attributes that can help us tackle the most complex issues in programming projects:

  • True Immutability: Data structures should be immutable, and the details of maintaining a revision history for data structures should be an abstracted detail, like memory management is in most modern languages with garbage collection / reference counting.
  • True Composability: OO languages purport to offer a way to re-use code, but the mechanisms for doing so often rely upon type inheritance; in functional languages, composability falls naturally out of having simple functions with immutable inputs and outputs and higher-order functions for laying out their execution order.
  • True Scalability: Most traditional languages either assume only operating in a single core, or provide low-level mechanisms for working with threads and locks. The free lunch is over for single core, and threads and locks are too complicated to get right. Clojure bundles a Software Transactional Memory (STM) implementation, that, when combined with composable functions and immutable data structures, can simplify parallelism and concurrency.
  • True Productivity: Some languages attempt to solve the above problems with a restrictive or verbose layer of static typing. Others try to solve the problems with complicated toolchains and compilation. A truly productive language has a small core, an interactive development flow (typically oriented around a REPL), and declarative, concise code forms.

Notice that I put the word “True” in front of each of these attributes. This is because if I were to reflect on Python as a language, I’d say it has all these attributes. You can build programs in Python that center around immutable data structures, have composable functions. You can write Python programs that scale up, and you can do so with a high degree of developer productivity. But Clojure tries to make these attributes fall naturally out of using the language, through a slew of built-in facilities, rather than enforcement of these attributes being a conscious design decision of the programmer (as is often the case in Python).

Hickey’s forceful arguments in his presentations are that the above attributes matter more than you might think. Defaults matter. Whatever is default is widespread.

Consider immutable data structures. In Python, we can code defensively using the copy module. Some languages, like Java, have immutable data structures as a third-party library, such as Guava Collections and its Immutable Collections support. But neither of these are widely used. You might even get negative code reviews from your colleagues for using them excessively. But in Clojure, immutable data structures are the default, thus they are widely used. Mutability is the opt-in behavior that draws strange looks from your programming colleagues.

Likewise, you can write composable functional programs in Python, but it’s probably just as common to write object-oriented programs and class heirarchies. I actually recently wrote a module in functional style on my team, and a few of my colleagues thought it was pretty weird because the code had “so many different entry points”. One even proposed rewriting it in terms of classes for clarity. In Clojure, these debates (class vs function) never happen, because functions are seen as the superior unit of composition. The debate centers around how to name, organize, and structure functions, not whether to use them altogether.

You can scale most dynamic languages beyond a single core using distributed computation frameworks like Hadoop and Storm, but it’s not using built-in language facilities and generally involves complex mechanisms. In Clojure, going from one core to multi-core is usually just a matter of using a different higher-order mapping function. Going from single-node to multi-node also becomes easier to reason about, because shared state is rare in Clojure programs.

So, all of this is to say, Clojure is not a revolutionary language. But it is different — that’s for sure. If you are used to Python, there are lessons to learn from Clojure, its community, and its code. This post, walking through some “Clojonic” examples of “Pythonic Clojure”, could serve as a good starting point.

Clojonic iteration

This is my favorite starting Python program example:

nums = [45, 23, 51, 32, 5]
for idx, num in enumerate(nums):
     print idx, num
# 0 45
# 1 23
# 2 51
# 3 32
# 4 5

The equivalent code in Clojure could be written:

(let [nums [45 23 51 32 5]]
    (for [[idx num] (map-indexed vector nums)]
        (println idx num)))
; 0 45
; 1 23
; 2 51
; 3 32
; 4 5

If you squint, this code looks pretty similar, but there are some important differences. The main aesthetic difference is a few more parentheses and brackets. Beyond that, the Python programmer will observe that in the Clojure program, many aspects of the program are implied, rather than annotated by special syntax. Many Clojure proponents will say that Clojure has a “simple syntax”, but I think this is misleading. They don’t mean simple as in “easy to read without prior context”. They mean simple as in “unceremonious”. Perhaps they mean simple as a contrast to “baroque” (how you might describe Java or C++’s syntax). Clojure does have a syntax, but it is implicit from the layout of the code in the list data structures. I’ve decoded the above code visually below:

clojure_syntax

So, in Python, the code was a combination of lightweight data structures ([], aka list), functions (enumerate()), statements (for, print), and explicit syntax (nums = [...] binding, for idx, num unpacking). In Clojure, the code is a combination of lightweight data structures ([], aka vector), special forms (let), macros (for), functions (println), and implied syntax (let [nums [...]] binding, for [idx num] destructuring). Seems like just a bunch of different names for the same concepts — and indeed, in this simple program, Python and Clojure share a lot of language facilities in common. But, we can start to dig deeper on the Clojure version to make it diverge more from Python.

So, firstly, what’s the difference between the Python list and the Clojure vector? Immutability. They have a similar syntax, but when Clojure’s list-like data structure is created, it cannot change. Other functions can accept the list as input and produce a new list as output.

So, you might ask the question, “How do I add an element to the nums list in Clojure?” The answer is that you can’t. You must, instead, pipe the list through a function that might generate a new list. For example, the conj function will construct a new immutable list with the additional element, but leave the original list intact. In other words, there is no equivalent to the .append() method.

(let [nums [45 23 51 32 5]
      bigger (conj nums 75)]
    (println nums)
    (println bigger))
; [45 23 51 32 5]
; [45 23 51 32 5 75]

It would be as if we wrote a conj function in Python like this:

import copy
 
def conj(a_list, elm):
    new_list = copy.copy(a_list)
    new_list.append(elm)
    return new_list

You might then ask, isn’t that very expensive? If a_list is large, that has to create a full copy in-memory of all its elements. If you wrote a function like this in Python, your colleagues would give you strange looks.

But, in Clojure, these details are handled by the language itself and in an optimized way. It uses a trick called Persistent Data Structures, which leverage a technique called “structural sharing”. Essentially, all of the list values are modeled as a tree that is maintained internally by the language and run-time. The immutable vectors you see as a programmer are “views” of the same arrangement of in-memory elements.

Let’s now look closely at the replacement for the enumerate() built-in we used in the Clojure code above. We had to use the more verbose function call to map-indexed below:

(map-indexed vector nums)
; ([0 45] [1 23] [2 51] [3 32] [4 5])

This illustrates a Clojure higher-order function. The docs for map-indexed describe its operation:

Returns a lazy sequence consisting of the result of applying f to 0 and the first item of coll, followed by applying f to 1 and the second item in coll, etc, until coll is exhausted. Thus function f should accept 2 arguments, index and item.

The function we passed as f is vector, which is similar to list in Python — it simply creates a vector from its list of arguments. So, this higher-order function will make repeated calls of the following form to our nums elements:

(vector 0 45) ; => [0 45]
(vector 1 23) ; => [1 23]
(vector 2 51) ; => [2 51]
(vector 3 32) ; => [3 32]
(vector 4 5)  ; => [4 5]

And this gives us our pairs of “indexed” elements. We could convert this higher-order function call into another function that operates more like the Python enumerate() counterpart.

(defn enumerate [coll]
    (map-indexed vector coll))

And we could then use (enumerate) in our Clojure code as follows:

(let [nums [45 23 51 32 5]]
    (for [[idx num] (enumerate nums)]
        (println idx num)))

There, that’s looking a bit closer to the Python. Now, the astute Python programmer will observe that the enumerate() built-in in the Python language is actually an iterator over the indexed values. It lazily returns the enumerated (indexed) values from the passed-in sequence. You could write you own version of enumerate by using a generator.

type(enumerate(nums))
# enumerate
enumerate(nums).next()
# (0, 45)
list(enumerate(nums))
# [(0, 45), (1, 23), (2, 51), (3, 32), (4, 5)]

How does our Clojure version compare?

Well, here comes the next surprise. In Python, you opt-in to lazy iteration by writing your own iterators or letting the language write iterators for you by using generator functions via the yield keyword. In Clojure, functions operating on sequences are lazy by default.

It turns out our Clojure enumerate function is more like a Python generator than it appears. This is because map-indexed “returns a lazy sequence”. It’s only once the sequence values of enumerate are fetched, an element-at-a-time, that they are evaluated as index pairs. This is done by the for macro, which is itself lazy. From the docs, the for macro “yields a lazy sequence of evaluations”. The for macro in Clojure is actually closer to a generator expression in Python than it is to the imperative for looping statement.

Indeed, some Clojure programmers will question my reasoning for using the for macro as an iteration construct. There is another construct, doseq, that has a similar interface, but is meant to be used for iteration. From the docs:

Repeatedly executes body (presumably for side-effects) with bindings and filtering as provided by “for”. Does not retain the head of the sequence. Returns nil.

So, the following code would be a more idiomatic form of looping:

(doseq [[idx num] (enumerate nums)]
    (println idx num))

Another Clojure programmer may also look upon the doseq and suggest it is a bit too verbose, not taking advantage of the fact that often iteration is achieved in Clojure via higher-order functions. The following code is just as good, and perhaps more idiomatic for Clojure:

(map println (enumerate nums))

Or, even the following, which may look cryptic now but will be explained shortly:

(->> nums enumerate (map println))

Beyond Python constructs with macros

I mentioned that for is a macro in Clojure. Another macro I used briefly was defn, which is a macro that composes the def (global variable declaration) and fn (function) forms. The cryptic ->> symbol in the last example above is also a macro, called “thread-last”.

Macros may be the most fascinating feature of the Clojure language to advanced Python practitioners who have gotten a lot out of Python’s metaprogramming facilities, such as decorators, context managers, and metaclasses.

Macros are like a generalization of these features. They give you, the programmer, a generic compiler hook to transform code. In short, you can make constructs that look like function calls, but operate as code transformations. These are both powerful, and dangerous.

Let’s go back to our enumerate example. I could actually rewrite the enumerate function as a macro, as follows:

(defmacro enumerate [coll]
    `(map-indexed vector ~coll))

The first backtick indicates that this is a literal list. It should not be evaluated as a function call. It should not call map-indexed upon evaluation.

The ~coll is telling Clojure that the input to enumerate should be placed, untouched, in the location at ~coll. So, in plain English, this says, “Define a macro, enumerate, that looks like a function, but actually gets replaced, at compile-time, with a function call to (map-indexed vector coll), but where coll is taken from the argument list of enumerate at compile-time.”

In other words, the body of the macro is like a template for a code replacement operation, and doesn’t have the overhead of a function call.

This is a pretty trivial example, but let’s explore it a little. Clojure provides macroexpand, which will show what the given macro call actually evaluates into at compile-time.

=> (macroexpand '(enumerate [1 2 3]))
(clojure.core/map-indexed clojure.core/vector [1 2 3])
; Translation: the code above is *replaced* with:
(map-indexed vector [1 2 3])

In this case, the overhead of the function call is probably negligible, so the complexity of the macro probably isn’t worth it. But anytime you think, “this group of functions has a lot of code repetition” — where in Python you might reach for decorators or context managers — you can use Clojure’s macros to achieve similar feats of code re-use. This gets at Clojure’s notion of True Productivity.

Let’s go back to the ->> thread-last macro from before. This macro does nothing more than rewrite the code that comes after it in “pipeline form”. Instead of thinking of “mapping the println function over the lazy sequence of indexed pairs generated by enumerate”…

(map println (enumerate nums))

… it might make more sense to think of it as “pipe the numbers through a function that generates index pairs, then pipe those pairs through a function that prints the results”:

(->> nums enumerate (map println)))

And indeed, these are equivalent formulations of the same code, thanks to the thread-last macro!

Working through a bigger example

So, given this introduction, Clojure probably feels very different, but also somewhat familiar. Let’s look at a bigger example program in Python:

# in twitter.py
import json
 
def with_twitter_data(filename, rdr_fn):
    with open(filename) as rdr:
        return list(rdr_fn(rdr))
 
def read_tweets(rdr):
    for line in rdr:
        apikey, timestamp, entry = line.split("|", 2)
        yield apikey, timestamp, json.loads(entry)
 
with_twitter_data("data/tweets.log", read_tweets)

This example defines two Python functions, with_twitter_data and read_tweets. The read_tweets function takes an iterator of lines that are formatted as follows:

1|2014-10-31|{"user": "amontalenti", "tweet": "some text"}

It splits these lines to get the strings apikey and timestamp and the parsed Python dictionary representing the JSON data, entry. It yields (apikey, timestamp, entry) as a 3-tuple lazily by using a generator function.

Meanwhile, with_twitter_data takes a function as an argument that knows how to parse those log lines, and eagerly evaluates the parsed results into an in-memory list. It then closes the file.

This code can be ported to Clojure very easily:

;; in twitter.clj
(ns twitter
    (:require [clojure.data.json :as json]
              [clojure.java.io :as io]
              [clojure.string :as str]))
 
(defn with-twitter-data [filename rdr-fn]
    (with-open [rdr (io/reader filename)]
        (doall (rdr-fn rdr))))
 
(defn read-tweets [rdr]
    (for [line (line-seq rdr)]
        (let [[apikey timestamp entry] (str/split line #"|" 3)]
            (vec [apikey timestamp (json/read-str entry)]))))
 
(with-twitter-data "data/tweets.log" read-tweets)

What’s different? Well, Clojure’s import facility is a little different. We use the ns macro to declare a namespace, which is similar to a Python module that is implicitly defined using module files. The :require [namespace :as alias] clause is equivalent to Python’s import module as alias syntax.

We can see that read-tweets likewise lazily evaluates the lines of the input rdr, and uses str/split and json/read-str to parse the log lines. Vectors are yielded back lazily thanks to the for macro.

The with-twitter-data function uses with-open, which works similarly to Python’s combination of the with keyword and the open() context manager. It automatically closes the file when it’s done processing. But it implements this using a Clojure macro that surrounds the body of your code with a (try) ... (finally) exception handler and a call to close.

The call to (doall (rdr-fn rdr)) may seem curious. This is forcing eager evaluation, similar to Python’s typical use of list() to materialize all the values of a lazy sequence. It’s saying, “repeatedly call rdr-fn on the lines of input until there are no more lines left.”

Similarities and differences

My exploration of Clojure so far has made me realize that the languages share surprisingly more in common than I originally thought as an outside observer. Indeed, I think Clojure may be the most “Pythonic” language running on the JVM today (short of Jython, of course). Let’s look at the similarities:

quick_compare

However, the Clojure language also brings many new ideas to the programming community, while also improving upon ideas found in prior languages (like Common Lisp). I tried to summarize many of the core differences I’ve observed here:

clojure_unique

I didn’t cover all of these differences, but we did take a look at Macros, Immutable Data Structures, Lazy Evaluation, and Code as Data very briefly. You can probably already see how a language with true immutability, composability, scalability, and productivity can emerge out of these building blocks.

If you are interested in going further down the Clojure rabbit hole, you’ll probably enjoy some of the additional resources I’ve curated below. I found these particularly helpful to me as a Pythonista exploring the world of functional programming through Clojure.


Articles

Videos

Books

The two best books out there seem to be:

There is also a free book out that explores Clojure from a beginner’s perspective, called Clojure for the Brave and True.

More materials related to this post

  • Slides on “Clojonic: Pythonic Clojure”: I put these slides together for a Parse.ly team retreat in Montreal, Canada and also presented them at a local functional programming meetup. This blog post was derived from the content of these slides.
  • Extended notes on the slides: The note form is quickly scannable and may be easier to copy-paste code examples form.

31 thoughts on “Clojonic: Pythonic Clojure”

  1. I’m by no means a Clojure expert, and you mention that others might question your use of the for macro, but you still list it as clojure’s basic iteration construct in the chart at the end. My limited experience has been that idiomatic iteration is usually accomplished with either map alone or with clojure’s tail recursion construct loop, i.e.:
    (loop [nums [45 23 51 32 5]
    i 0]
    (println i (first nums))
    (recur (rest nums) (inc I)))

    or, inside a let binding for nums,

    (map-indexed #(println % %2) nums)

    using the anonymous function literal.

    If you’re drawing up a chart, the for macro seems more equivalent to python’s list comprehensions than a basic iteration construct. I understand the goal of making Clojure approachable to Python programmers, but the omission of loop seems over the top.

    Thanks for an interesting post! Sorry for nitpicking — it’s only cause you got me thinking.

  2. Nice comparison, just one nitpick:
    In the first example, it is not good practise to use ‘for’ with a side-effecting function (println), as it returns a lazy sequence and the body may not get executed. It will look OK in the REPL, since it will get evaluated when it’s printed out, but in other cases it might do nothing. You should use ‘doseq’ instead, it wrap the whole thing in a ‘dorun’.

  3. Great post Andrew! You call attention to for being lazy; you could go further and explain the implications: 1) Unless the result is consumed, nothing will be printed (the result is consumed by the repl if you evaluate these examples, but in a larger program such as a function that returns a different result, they might not be.) 2) the return result is a sequence of nil, printing is a side effect. 3) (str (map + (range 10))) => “clojure.lang.LazySeq@9ebadac6”. New Clojure users often struggle with these behaviors. If you want to io such as print, use doseq instead of for or map. I understand for your example for is more interesting and not suggesting you change that part, just suggesting that showing the behavior of laziness might help people avoid this common pitfall. Here is an example SO question http://stackoverflow.com/questions/14062541/why-does-clojures-map-behave-that-way-with-println, there are many of them.

  4. A small nitpick, in the let form, what is created is not a var, it is a local binding. Vars are mutable, and in normal code you will only see them as globals created by def or defn.

  5. Nice intro to Clojure for Python programmers.

    The big win I value most in Clojure is conciseness. For instance, I would have written your first example as

    (dorun (map-indexed println [45 23 51 32 5]))

  6. All the points about the (for) macro not being equivalent to Python’s for statement are definitely well-taken. After realizing this, I added a section to the post about using doseq instead of for for side-effectful iteration.

    Perhaps I’ll update the chart/table soon to mention both “for” and “doseq”. As others have pointed out, there are certainly no shortage of iteration constructs in Clojure. I count (dorun map ...), (doseq), (for), & (loop ... (recur)) in this comment thread, alone!

  7. Thanks for giving us the big pyjure!

    As a Rubyist who has been trying to tackle both Python and Clojure recently, I found your article very helpful.

  8. I’d be cautious about drawing any analogies between Python modules and Clojure namespaces. Modules are tied to files, and are closed for extension once created. Clojure namespaces are true namespaces – you jump in can into them, jump out of them, manipulate multiple namespaces within a single file, etc. They are first-class entities within the Clojure world.

  9. Paul deGrandis makes a good point, about Clojure’s namespaces being flexible. A Clojure namespace can include multiple files. I am looking, but not finding, a Python equivalent to Potemkin:

    https://github.com/ztellman/potemkin

    “import-vars” is similar to Python’s “from” mechanism (as in “from HttpRequest import ContentType”) but “import-vars” allows some creative aliases, to avoid name collisions even when one erases directory name’s from the namespace name.

  10. I felt a sinking feeling when I read Emlyn’s comment ‘It will look OK in the REPL… but…’. This hints at the sort of gotcha that causes so many hard-to-diagnose bugs in other languages (C++, I’m looking at you…)

    I use C++ and I have in the past used Lisp and Python, among others, but I haven’t looked at Clojure yet – it’s on my long and slow-moving todo list.

  11. araybold, I would not describe it as a hard to debug problem. It’s simply not idiomatic in Clojure.
    However, using “str” to return a sequence of concatenated strings is perfectly idiomatic, because you want the sequence that is returned, so this is fine:

    (let [nums [45 23 51 32 5]]
    (for [[idx num] (map-indexed vector nums)]
    (str idx ” : ” num)))

    which returns a sequence of strings:

    (“0 : 45” “1 : 23” “2 : 51” “3 : 32” “4 : 5″)

    And if you printed the final result, that would also be idiomatic:

    (clojure.pprint/pprint (let [nums [45 23 51 32 5]]
    (for [[idx num] (map-indexed vector nums)]
    (str idx ” : ” num))))

    (“0 : 45” “1 : 23” “2 : 51” “3 : 32” “4 : 5”)

    As Andrew showed, you can use printlin inside of “for” but I wouldn’t recommend it, since the “for” always returns a sequence, but printlin returns “nil”, so you end up with a sequence full of 5 nils. (It’s a bit of a nitpick semantic thing, but technically there are no statements in Clojure, only expressions that return a value, so “println” returns nil.)

    But, as I said, using “str” to return a sequence of concatenated strings is perfectly idiomatic.

  12. Why is the clojure string literal #”|”?

    Also it’s not fair to say that Python DSLs use metaclasses. Metaclass is a bit of a loaded word, but only certain kinds of DSL constructs require he use of a metaclass. The much more common construct is to override some special method on a class. You only need a metaclass if you are trying to do certain classes of things not supported directly by Python classes (and that arguably makes them unpythonic).

  13. @Aaron: the Clojure literal is #"\|" because Clojure’s clojure.string/split function takes a regular expression pattern rather than a raw string. Clojure provides a literal syntax that compiles java.util.regex.Pattern objects. I need to escape the pipe character because in regex, the pipe has meaning and I want to split on a literal "|".

    See split and re-pattern functions for more information.

    Perhaps there’s another way to do it?

  14. @Aaron, also, re: metaclasses and DSLs, I find that, in general, Pythonistas rarely write “DSLs” as commonly understood, because it’s complicated to hook into Python’s AST. I think Metaclasses, Decorators, Descriptors, and Context Managers are the features that advanced Python programmers sometimes use to implement something that looks like a DSL. For example, SQLAlchemy’s support for class-based table definition leverages Metaclasses and Descriptors. And Flask’s implementation of function-based view dispatching uses decorators. So perhaps you’re right that metaclasses isn’t really the main “DSL construct” in Python, but it’s definitely one of the available ones, and perhaps the one that allows the most customization of what people might consider a “code-level transformation”.

  15. I consider any overriding any method to be creating a DSL. Some are more extreme than others (e.g., the arithmetic operators, or extensive use of getitem). It is true that you are limited by the Python grammar. You can’t introduce new binary operators, or change the precedence of the existing ones for instance. To me, all DSLs that live inside another language are inherently limited. Otherwise, you aren’t really writing a DSL in a language, you’re implementing a compiler.

  16. Aaron Meurer, you ask:

    Why is the clojure string literal #”|”?

    That is not accurate. This is a string literal:

    “hi”

    This is the literal for the pattern in a regular expression:

    #”hi”

    You could use this as:

    (clojure.string/replace “Hi Aaron” #”Hi” “Hello”)

    You would end up with “Hello Aaron”.

  17. Aaron:

    Otherwise, you aren’t really writing a DSL in a language, you’re implementing a compiler.

    You say that like it is a bad thing!

    I’m half kidding, but consider the famous Lisp saying “Lisp is not the language that you will use to solve your problem, rather, Lisp is the language that will allow you to build the language that will solve your problem.” There are some advantages to building specialized languages, that is why stuff like Rake and SQL exist.

  18. I think you missed the point of why people call Clojure syntax “simple”. It is not a matter of ceremony. It is called simple because it is uniform and has few special cases, making it easy to extend and interface to.

    Instead of having built-in constructs (such as for – yield), you have a few elementary components to the syntax, and macros to arrange them in a way that suits your needs. One advantage is that you don’t have to petition the authorities of the language to introduce handy syntax constructs; another is that macros enable you to fully separate the concerns of program structure and code aspect.

    In this regard, it is fortunate that Clojure’s syntax has so few building blocks, because it simplifies the task of building macros on top of it (such as ->>).

    One last (and probably underestimated) advantage of uniform syntax is ease of editing : I have found it takes less focus when you only have to juggle with a few commands. Of course, you have to try it to believe it 🙂

  19. When you think it’s impossible that someone be stupid enough to reinvent the wheel and create a horrible thing inspired in a really nice one, then you know Clojure and its egocentric (and needy for attention) creator, which I refuse myself to say his name.. I’m converting Clojure code to Python, and it’s abysmal the similarity between them, with the difference that Python is far easier and nicer to program… No Clojure feature should justify its use…

  20. @David Ragazzi: anyone somewhat familiar with the Clojure ecosystem would know everything from this comment is just plainly wrong. Given its hateful tone, I know it would be pointless to try

  21. @David Ragazzi: no one somewhat familiar with the Clojure ecosystem would take this comment seriously. Having said that, I would not want Python programmers to be misguided by it, so allow me to reply.

    1) Rich Hickey spent a 3-years unpaid sabbatical carefully growing the language. In most of the design choices he made, he favoured the long-term benefit to the programmers at the expense of the short-term popularity of the language (ex: no class inheritance. Go say that to a Java crowd in 2008). He released it in open-source. Finally, if you count his public appearances, you’ll notice they are rather rare (he made no talk at all in the last Clojure conj). This does not strike me as “egocentric and needy for attention”.

    2) Maybe the particular code you converted was very Python-like, good for you. But one quick look at Cojure’s feature is enough to tell it’s obviously NOT a reinvention of Python. Clojure’s polymorphism constructs, managed references, homoiconicity and functional emphasis are essential features which have no equivalent in Python. Likewise, Python’s class system has no equivalent in Clojure. If that’s still too similar to Python to justify a new language, you must hate a lot of languages!

    I know rewriting a system comes with a lot of frustrations, but you’re embarassing yourself (and misrepresenting the Python community) with this sort of hateful comment. My advice: when you’re angry, don’t blog, just go to the gym.

  22. I am surprised by the level of heat that David Ragazzi brings to the conversation. I think all of us run some risk of getting so used to one eco-system that other eco-systems become strange to us — not merely foreign, but hateful to us. But there is something to be learned from “straunge strondes”. What seems exotic sometimes reveals news ways of doing things. Certainly, Clojure remains the only “popular” language that combines a default of immutability with dynamic typing. And the argument for or against Clojure is mostly a debate about whether those 2 features belong together.

    I’ve tried to make that argument myself, though my rough drafts are always verbose, and I only seem to have time to write rough drafts. Some day I will go back and edit this down to size:

    http://www.smashcompany.com/technology/functional-programming-is-not-the-same-as-static-data-type-checking

  23. Laurence,

    If you think that I’m Python guy and probably hate other languages than Python, sorry to disapoint you… I worked with Java, C#, C++, VB, Pascal, etc, and feel more confortable with them than Clojure. So I have no dificulties to learn new languages and new eco-systems.

    The questions is that there’s a natural trend and ill of companies and professionals to improve production and maintanance of projects. So it is desirable that a tool do the same thing than other existent in a simple and clean way. This is good for everyone because everyone gains. Note that the own C++11 standard makes C++ code more readble following this trend.

    Because this you see most startups using Python/Django as main language for their projects. Recently top universities are adopting Python over Java because this helps newbies (including not computer/IT students) to get used to programming language.

    When I see people advocating and making proselytism for Clojure, this is annoying because it sounds that this “super” language do more great things than other ones, what IS NOT TRUE, it just does in a diferent (and bizarre) way. So why you Clojure guys still insist on this?

  24. Pingback: The Blog Chill

Leave a Reply