Towards a more Pythonic "Ternary Operator"

Clark C. Evans cce at clarkevans.com
Wed Mar 5 01:59:06 CET 2003


Alex, Gustavo, and Carel,

  Thank you for commenting.  Below is follow up to your response
  and an improved "operator" mechanism for the case/switch statement
  due to musings on Carel's option.

On Tue, Mar 04, 2003 at 04:05:12PM +0100, Alex Martelli wrote:
| Good points -- basically similar to arguments for having
| such augmented-assignment operators as += in the language
| (avoiding <variable> = <variable> <operator> <value>
| redundancy both in reading and writing the code).

Yes. 

| > So, really, we have two categories of duplication:
| >    
| >    (a) the left hand side of the assignment
| >    (b) the left hand side of the conditional
| 
| Yes, but they're orthogonal -- i.e. the second case might
| just as well be:
| 
|       if a == X:
|          c = Q
|       elif a == Y:
|          d = R
|       else:
|          e = S
| 
| which only shows redundancy kind [b], not kind [a].  Why
| are we assuming the two redundancies go together?

Correct. However, empirically from my production code 
(in production at 20+ companies), both types of redundancy
tend to cluster together.  Thus, it makes sense to me to 
try and think of a mechanism which allows us to treat them
together.  Think of it as an 80/20 optimization.  For example, 
I often have code like...

  if   0 == quantity:  exit = 'no exit'
  elif 1 == quantity: exit = 'a door'
  else: exit = '%s doors' % quantity

which I often re-write with the "mapping-idiom" as:

  exit = { 0: 'no exit',
           1: 'a door' }.get(quantity,
          '%s doors' % quantity)

This gains the advantage of normalizing both type [a]
and type [b] redundancies, however, it comes at a cost:

  1. the code isn't "obvious" to newbies making it
     harder to maintain -- making my code obscure

  2. the code doesn't short-circuti;  Yes, you can have
     function pointers returned from the mapping and
     then invoke the function... but this isn't any
     more obvious or easy to read!
     
Thus, a new syntax-level construct which combines this
pattern in a easy to understand manner /w short-circuts
would be useful...

  exit = select quantity
            case 0: 'no exit'
            case 1: 'a door'
            else: "%s doors" % quantity

The examples given in the survey are very cleanly 
expressed with this syntax.

  data = select hasattr(s,'open')
           case true: s.readlines()
           else: s.split()

  z = 1.0 + select abs(z) < .0001
              case true: 0
              else: z

  t = v[index] = select t <= 0
                   case true: (t-1.0)
                   else: -sigma / (t + 1.0)

  return select len(s) < 10
           case true: linsort(s)
           else: qsort(s)

| > Perhaps we can scratch two itches at the same time?  The syntax
| > really doesn't matter now; what matters is that we agree on the
| > problem.  Then we can throw up our best syntaxes and let Guido
| > choose and work his magic...
| 
| I like this analysis, but I do not understand the reason to
| assume the two redundancy problems should occur together.

I've found a slight hybrid of the combination common, where
[a] and [b] occur, but the 'operator' differs.  In this 
situation, we could allow an optional boolean function/operator 
right after the case statement.  The first argument to the operator
would be the item selected, and the remaining arguments are used
up to the colon.  If the operator is missing, it is assumed to 
be the '==' operator.

  z = 1.0 + select abs(z)
              case < .0001: 0
              case > .5: .5
              else: -sigma / (t + 1.0)

  score = select die
            case 1: -2
            case 2: -1
            case between 3,4: 0
            case 5: +1
            case 6: +2
            else: raise "invalid die roll %d " % roll

   For a die roll of 8, for example, the 'between' 
   function would be called with the tuple (8,3,4)
   and would clearly return false.

On Tue, Mar 04, 2003 at 09:09:52AM -0600, sismex01 at hebmex.com wrote:
| Yes, there is a lot of duplicated "text" in the first
| case, and the second case corrects that.  But you don't
| need new syntax to be able to make it better, you already
| have dictionaries:
|
|  b = { Q:X, R:Y }.get(a,S)
|
| At least to me, it's a lot more readable, doesn't involve
| new syntax, and can grow to a larget amount of options
| if needed.

While it may be common, this idiom isn't very intuitive
for newbies.  It took several examples before I caught
on... and even then I'm not really happy with the result.

| In the above example, imagine evaluating A, B or C could
| have side-effects; in that case, upon constructing the
| selection dictionary, all side-effects would have been
| triggered, so this construction would be no good.  It
| could be adapted, by changing the function chooser()
| like so:
|
| >>> def chooser(option):
|       try:
|         return { a:lambda:A,
|                  b:lambda:B,
|                  c:lambda:C }[option]()
|       except KeyError:
|         return default

Yes, but once again, this isn't obvious to newbies
nor is it very readable.  In short, ick.

On Tue, Mar 04, 2003 at 04:27:47PM +0100, Carel Fellinger wrote:
|
| I think you missed
|
|      (c) it takes up too much precious screen estate
|

Exactly.  I purposefully left his one out.  I think that much of
the Python community would disagree with you here.  Python isn't
and shouldn't be horizontally efficient (like C or Java).

| otherwise I agree, and I think a general switch expression might
| be more interesting, but as you propose the beast it only deals
| with straight comparision.  What about enhancing it like:
|
|   b = (if a <op>:
|        on X: Q
|        on Y: R
|        else: S)

I merged your idea of the operator with my idea of the predicates,
see above.  Thanks.  This seems to be an improvement.  I'm not yet
certain that it is worth the complexity, but it is nice.

| And a possible shortening syntactic sugaring could produce:
|
|    b = (if a <opt>; on X: Q; on Y: R; else: S)

Here is where you add your (c) requirement, and I think it is
also the point where a good part of the Python community
(who came to Python for clearly indented logical structures)
disagrees.

| Anyway, by now it looks so troublesome that I rather not have it in:)

No doubt.  But I think where you went wrong is that (c) isn't 
a requirement.  Python need not be horizontally efficient.

Best,

Clark





More information about the Python-list mailing list