PEP 308: an additional "select/when" survey option

Clark C. Evans cce at clarkevans.com
Tue Mar 4 23:42:42 EST 2003


After a side discussion, it seems that this construct is being
confused with C's switch/case.  It is quite different than switch/case
since it is an expression (has a value) rather than a block construct
containing statements.  It's actually more like SQL's CASE/WHEN, 
which, btw, has two forms:

  CASE WHEN value = 0 THEN 'zero' 
       WHEN value = 2 THEN 'one'
       ELSE 'unknown' 
  END

and...
 
  CASE value
    WHEN 0 THEN 'zero'
    WHEN 2 THEN 'one'
    ELSE 'UNKNOWN'
  END

So, I was thinking about three modifications to the proposal,

  1. Change 'case' to 'when' so that confusion with 
     switch/case is harder to make.

  2. Allow the 'select' keyword to be missing so that 
     the 'when' items can be plain old boolean expressions
     like the 1st case above.
  
  3. Make it clear in the motivation that this is an 
     expression (with a value) not a block structure;
     as a result, this greatly improves the power of
     lambda functions which as I understand can't
     contain statements, and thus can't do if/then
     sorts of logic.

The updated proposal/announcement...

-----

After some encouragement by Raymond, I'd like to add one more
item to the survey, if you like what follows perhaps you can
even *change* your vote (Raymond?) to include:

   Q accept  select/when
   Z deny    everything else!

Dedication:

   To those who hate the terinary operator beacuse it isn't pythonic...

Background:

   After looking over much of my Python source code, I found that 
   where I thought I needed a terinary operator, I ended
   up taking one of two paths:

      if   0 == quantity:  exit = 'no exit'
      elif 1 == quantity: exit = 'a door'
      else: exit = '%s doors' % quantity
      
   Or, the more concise mapping-idiom:

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

   The latter construct has two advantages over the if/else
   statement level solution:

     1. It's clear that I'm making an assignment to exit;
        in effect the "exit =" isn't duplicated and this
        aids in authoring/maintenance/reading.

     2. The ugly '== quantity' isn't duplicated for each line,
        once again improving maintenance.


   However, the mapping-idiom has three problems:

     1. The conditional switch isn't exactly "obvious" here
        unless you're a Python vetran, this hurts in the 
        maintenance arena; besides the 'else' case is ugly.

     2. It doesn't short-circut cleanly, to do a short-circut
        you need to use lambda's... ick; further, it results in
        the construction of a mapping which may not really help
        out optimizations.

     3. It really doesn't facilitate the use whitespace/indentation
        to re-inforce a visual representation of the program's 
        structure.

Philosophy:

   The whole rationale for this construct is to introduce
   a conditional 'expression' construct, and further to provide
   this construct in such a way as to reduce duplication (of both
   assignment and conditional fragments) to increase maintenance.

   The goal is not to save on vertical screen realistate by
   enabling multi-line constructs to be "jumbled" into a 
   single line.   It seems that many people are asking for the
   terinary option for the latter.  

   Instead, this proposal seeks to engage Python's unique approach
   to syntax by using whitespace to enhance the visual representation
   of the program's structure.   Many Python converts are here exactly
   beacuse Python is very readable and thus maintainable.  This proposal
   is here soley to re-enforce this "pythonic" approach to coding.

Proposal:

   The proposal introduces a 'select' or 'switch' keyword which creates
   an indented expression block.  Instead of the following,

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

   You could write,

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

   This proposal gives you the power of the mapping-idiom without
   the uglyness.  It expresses the intent of the construct in a 
   very human readable manner using whitespace smartly.

   While the above is "good", it assumes an equality operator.  So
   that the structure is more generic, his proposal allows an optional
   operator (a function taking 2 or more value and returning a boolean)
   immediately following the 'case' label,

     z = 1.0 + select abs(z)
                 when < .0001: 0
                 when > .5: .5
                 else: -sigma / (t + 1.0)
   
   Note that the examples given in the proposal are thus
   very easily expressed using this notion:

     data = select hasattr(s,'open')
              when true: s.readlines()
              else: s.split()
 
     z = 1.0 + select abs(z)
                when < .0001: 0
                else: z

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

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

   The 'operator' need not be binary, one could, for example,
   provide a terinary operator, such as:
  
     def between(cmp,rhs,lhs): return cmp >= rhs and cmp <= lhs
 
     score = select die
               when 1: -2
               when 2: -1
               when between 3,4: 0
               when 5: +1
               when 6: +2
               else: raise "invalid die roll %d " % roll

   As a further (quite optional) enhancement, allow 'select'
   keyword to be absent, and in this case, the 'when' statement
   is evaulated as a boolean expression.

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

Summary:

  The proposal thus creates a flexible mechanism for avoiding
  the excessive duplication of assignment and equality fragments
  within a conditional assignment nest; it does so through a
  new expression structure which, like the rest of Python, uses
  indentation for structure.

  This proposal thus provides the pratical benefit of a terinary
  operator, while at the same time opening the door to a rich 
  (and quite readable) conditional assignment mechanism.   As a
  side effect, it could make lambda functions more powerful.

  In particular, this proposal rejects the rationale for 'terinary'
  operator as a way to trade horizontal screen realestate for
  vertical space; as this rationale is in direct opposition to 
  the fundamental principle of readability.  And as such, the 
  proposal explicitly does not include a way to include multiple
  case labels on the same line.

Credits:

  Alex Martelli   for validating the elimination of needless-duplication
                  of code as the primary goal for the construct; and

  Carel Fellinger for presenting the idea of a plugable predicate/operator

  Raymond Hettinger for asking me to write this up more or less formally





More information about the Python-list mailing list