[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: expressions vs. statements



(I wondered why nobody had responded to this post.  Then I discovered
it was because I hadn't sent it.)

Paul Graham writes:
> This is actually a question people on this list might
> have interesting opinions about.  Does distinguishing 
> between expressions and statements buy you anything in 
> expressive power?  I have often wondered about what
> the point of this distinction was...

In Python, it buys you three things; whether or not they constitute
expressive power is debatable.  At least one of them is a deliberate
restriction on expressive power, by some definitions.

1. Python uses == for testing equality, like C.  People, even
experienced programmers who have been using Python and C for many
years, tend to forget every once in a while and write = instead.  Not
allowing = in expressions makes this a syntax error instead of a
semantic bug.

In general, restrictions and redundancy in syntax and semantics make
it easier to detect programming errors and provide useful diagnostics
for them.  The right degree of restriction and redundancy depend
partly on the user and partly on the particular errors being caught.

2. It means syntax that means one thing as a statement can mean
something else in some other context; while this can lead to code
that's harder to read, in Python it means we can use = for passing
named parameters to functions.  I think that's actually a significant
win in notation over using, say, : or @, because the value on the
right side of = does in fact get bound to the name on the left in the
called function's namespace.

3. The restriction on expressive power is that it makes
hard-to-understand code more verbose, by attaching a syntactic penalty
to side effects, so that people don't tend to write it as often.  For
example, people tend to write variable definitions and increments in
separate statements instead of burying them in some other expression.
This arguably results in clearer code.  (This isn't much of a problem
in Lisp dialects, because binding a variable requires a binding form
like a let, and at least in Scheme, set! has an undefined return value
and tends to return useless values.)

For the same reason, Python object methods that mutate things tend to
return None, which makes it less likely for people to use them in
expressions.

But sometimes I write Python code like this test case, which
demonstrates why I wish I had macros, inline first-class code blocks,
or the unification of expressions and statements:

    def testBoundsChecking(self):
        # other tests like testSetUp check bounds checking on reads
        def lambdaSucks(self=self): self.lw[4] = 1  # lambda sucks in Python
        self.failUnlessRaises(IndexError, lambdaSucks)
        def lambdaStillSucks(self=self): self.lw[-5] = 1
        self.failUnlessRaises(IndexError, lambdaStillSucks)
        def lambdaSucksBad(self=self): del self.lw[4]
        self.failUnlessRaises(IndexError, lambdaSucksBad)
        def moreLambdaSuckage(self=self): del self.lw[-5]
        self.failUnlessRaises(IndexError, moreLambdaSuckage)

Assignment and deletion are two operations in Python that can't be
done in an expression, and lambda encloses expressions, not
statements.  So I have to name the statements that should give rise to
IndexError.  I'd really much rather say
failUnlessRaises IndexError (lw[4] = 1).