I'd like to convince some Python developers that Clojure is not foreign and does many things better than Python. I'd appreciate whatever suggestions you have about what I've written.
Though I am crazy about STM and Clojure agents, I don't deal with them here. This has to be a gentle introduction rather than a thorough survey of Clojure, so I've left out other language features, too. Hello, world. [Clojure] (println "Hello, world") [Python] print "Hello, world" In a function. [Clojure] (defn foo [] (println "Hello, world")) [Python] def foo(): print "Hello, world" A list (in Clojure, a vector) of three integers. [Clojure] [1 2 3] [Python] [1, 2, 3] A hash-set of three integers. [Clojure] #{1 2 3} [Python] set([1, 2, 3]) A hash-map. [Clojure] {0 false 1 true} [Python] {0 : False, 1: True} Test for the equality of two collections. [Clojure] (= [1 2 3] [1 2 3]) [Python] [1, 2, 3] == [1, 2, 3] Clojure tests for the value-equality of collections, just like Python does. Test for the equality of two distinct objects. [Clojure] (not= (new Exception) (new Exception)) [Python] Exception() != Exception() Again, Clojure works just like Python. Two distinct objects are not equal. Print the integers 1-10. [Clojure] (doseq [i (range 1 11)] (println i)) [Python] for i in range(1, 11): print i Try calling foo with x; call bar with an AssertionError if one is thrown; and finally call baz. [Clojure] (try (foo x) (catch AssertionError e (bar e)) (finally (baz))) [Python] try: foo(x) except AssertionError, e: bar(e) finally: baz() Note that Clojure doesn't have its own exception type system. It simply lets you catch, throw, and reflect on Java's exception objects, directly. If x is true, call foo with y. Otherwise, call bar with y. [Clojure] (if x (foo y) (bar y)) [Python] if x: foo(y) else: bar(y) Same thing in a function. [Clojure] (defn baz [x y] (if x (foo y) (bar y))) [Python] def baz (x, y): if x: foo(y) else: bar(y) Same thing, but baz returns whatever foo or bar returned. [Clojure] (defn baz [x y] (if x (foo y) (bar y))) [Python] def baz (x, y): if x: return foo(y) else: return bar(y) The Clojure didn't change, because Clojure doesn't have return statements. A function simply returns the last form it evaluates. This makes it tricky to write a function that has a dozen different return points, which could be something you miss or something you're glad not to be tempted by, depending on who you are. The absence of return statements makes your Clojure functions feel referentially transparent, which, ideally they will be. If x is true, call foo and then foo2 with y. Otherwise, call bar with y. [Clojure] (if x (do (foo y) (foo2 y)) (bar y)) [Python] if x: foo(y) foo2(y) else: bar(y) The price of Clojure's not requiring else statements is that an if statement only takes three forms: the test, the form to evaluate if the test is true, and the form to evaluate otherwise. (It's fine to omit the last of these.) If you want any of these forms to do more than one thing, you need to wrap it in do, so it's recognized as a single form. Whitespace conventions make it still very readable. There are just two forms in the column inside if: the do block and the bar block. If you don't need an else block, you can replace if with when and drop the do, because when supplies one implicitly: (when x (foo y) (foo2 y)) Find the first match of "a." in "abac". [Clojure] (re-find #"a." "abac") [Python] import re re.search("a.", "abac").group() Clojure has regexp functions in the language core and gives you convenient #"<pattern>" syntax for regexp patterns. Python's re makes you call group() on a regexp match, which Clojure doesn't. Find the first match of "a." in the string x, which may or may not contain the pattern "a.". [Clojure] (re-find #"a." x) [Python] import re match = re.search("a.", x) if match: match.group() re.search() returns None if there is no match, which of course you wouldn't be allowed to call group() on. The Clojure is just a lot nicer. re-find simply returns the string you want or nil if there isn't a match. Take a dictionary of defaults and a user-supplied dictionary and return a new dictionary that combines them, with the user's entries winning when both dictionaries have the same key. [Clojure] (defn foo [defaults user] (merge defaults user)) [Python] def foo(defaults, user): d = defaults.copy() d.update(user) return d def foo(defaults, user): d = {} for k, v in defaults.items() + user.items(): d[k] = v return d It's not the end of the world, but Python doesn't have a nice way of merging dictionaries, like Clojure has. In Python, I could accidentally modify an input to foo, and I would, if I forgot to make a copy of defaults before calling update(). That mistake is impossible in Clojure, because all Clojure data structures are immutable. I don't have to consult the Clojure API documentation to find out whether merge does anything destructive to its inputs. It can't, because the inputs are Clojure data structures, so there's no risk of accidentally modifying them. Check whether every object in the collection x can be called like a function. [Clojure] (defn foo [x] (every? ifn? x)) [Python] def foo(x): for y in x: if not hasattr(y, "__call__"): return False return True def foo(x): return x == [y for y in x if hasattr(y, "__call__")] def foo(x): return x == filter(lambda y: hasattr(y, "__call__"), x) Both the Python foo and Clojure foo will work on any type of collection x. Clojure has two things Python doesn't: a higher-order function every? that returns whether a predicate (true/false-returning) function returns true for every element in a collection, and a predicate function ifn? that tells you whether its argument implements the function interface. Clojure has many more higher-order functions (like not-any?, some) and many more predicate functions (like nil?, number?, even?). A lot of what we do is manipulate collections, and functional programming just makes it better. There's room for error in Python. In the first (and best-performing) Python example, you have an opportunity to mess up and put return True inside the for block rather than after it. In the second Python example, you have an opportunity to mess up and call some useful but destructive method on y in your list comprehension, leaving the caller of foo with a modified x. Clojure is safer because its data structures are immutable, and its functional style makes your code more readable and far more direct. Think of the bugs you've seen that wouldn't have been there if you hadn't needed fancy nested loops with multiple return points or copy()s of collection objects. Functional programming makes it impossible to have these kinds of bugs. Read the contents of a file. [Clojure] (slurp filename) [Python] open(filename, 'r').read() # who cares about closing files opened in read-mode? with open(filename, 'r') as f: # i do f.read() Slurp is a reliable convenience. It uses a java.io.BufferedReader and java.lang.StringBuilder and closes your file even if an exception is thrown while reading, just like the Python context manager does. The point is that Clojure can be trusted to do file, network, and database io. It simply uses Java's io libraries. You can use them directly in Clojure, without the convenience of wrappers like slurp, if you want to. Call foo and bar, redirecting any standard output to a different output stream, x. [Clojure] (binding [*out* x] (foo) (bar)) [Python] import sys try: sys.stdout = x foo() bar() finally: sys.stdout = sys.__stdout__ Binding takes an already-defined global var, like *out*, and temporarily rebinds it to something new. The rebinding isn't just lexically scoped; it follows the call stack and lasts until the block of code wrapped in binding is completed. So if foo and bar do some printing and also call baz, which does some more printing, all of this output goes to x. The rebinding is done inside a try/finally block, so it's guaranteed to be lifted when the binding block is completed. Define z as the lazy Cartesian product of two collections, x and y. [Clojure] (def z (for [i x j y] [i j])) [Python] def foo(x, y): for i in x: for j in y: yield i, j z = foo(x, y) import itertools z = itertools.product(x, y) # new in Python 2.6 To get a lazy sequence in Python, you need to write a generator-function and then call it to get a generator, or use itertools. Either way, you get an object that can be iterated over lazily but can only be iterated over once. It's too easy to go wrong. Loop over z once, and you're great. Loop over it again, and nothing will happen: no action, and no exception. Clojure's for loops automatically result in lazy sequences. Loop over the Clojure z as many times as you want, in different contexts, and you'll always get the entire sequence. Clojure's lazy evaluation is simply much safer than Python's. Define a capturer object that can do two things: capture something, and return everything it's captured so far. [Clojure] (defn create-capturer [] (let [captured (atom #{})] {:capture (fn [x] (swap! captured conj x)) :get-all-captured (fn [] (deref captured))})) [Python] class Capturer(object): def __init__(self): self._captured = set([]) def capture(self, x): self._captured.add(x) def get_all_captured(self): return self._captured.copy() To get a Clojure capturer, you call (create-capturer). To get a Python capturer, you call Capturer(). create-capturer returns a map containing two functions. Look up the keyword :capture and you'll get a function that captures. Look up the keyword :get-all-captured and you'll get a function that shows you your captured objects. In this implementation, a mutable storage location called an atom points to your captured objects. This is one of the ways Clojure does mutable state. Your atom, captured, points to a hash-set. Hash-sets, like all Clojure data structures, are immutable. But you can repoint captured, your atom, to a different hash-set. When you call (swap! captured conj x), you create a new hash-set, containing x along with your already-captured objects, and you repoint captured to this new hash-set. When you call (deref captured), you get to peek at the hash-set that captured currently points to. No one using create-capturer is going to be repointing your atom directly. She can't, because create-capturer doesn't return captured directly. It returns functions that close over captured, and the only way anyone can deal with captured is by calling your functions. Users don't need to know that you're using an atom, nor do you need to worry that a user could try to repoint captured except through your :capture function. It's bullet-proof encapsulation, and it was easy to get. I haven't used atoms until now. I didn't need them for manipulating dictionaries, parsing strings, inspecting collections of callables, reading files, or redirecting standard output. Mutable state is a special design pattern in Clojure, and you normally don't need it. But when you do, you get a very simple system of mutable pointers to immutable data structures. Is the Python better? There's no notion of atoms, so it's simpler. You can just modify a capturer's _captured directly. But this means you have to hope that no one will. And in get_all_captured(), you have to remember to return a copy of _captured, because you can assume people will want to pop() or update() or clear() or otherwise mess around with what get_all_captured() returns. Why wouldn't they? The Clojure version is more concise and radically safer but a little more conceptually packed. Is it worth your trouble? --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en -~----------~----~----~----~------~----~------~--~---