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
-~----------~----~----~----~------~----~------~--~---

Reply via email to