On Fri, Feb 28, 2014 at 10:38 PM, Marko Rauhamaa <ma...@pacujo.net> wrote:
> Chris Angelico <ros...@gmail.com>:
>
>> Python currently has dispatch tables and if/elif chains, and a strong
>> cultural aversion to switch. You could change that by coming up with
>> some *really* awesome proposal, but you'll be fighting against the
>> tide a bit.
>
> It's easy have a "cultural aversion" when the language doesn't provide
> the facility.

I'm talking about the strong resistance that gets put up any time the
suggestion comes up on python-ideas or somesuch. The core devs and
Guido especially are against the idea.

> Switch statements provide for excellent readability in parsers and state
> machines, for example. They also allow the Python compiler to optimize
> the statement internally unlike long if-else chains.

It's usually possible to turn any concrete example of a switch
statement into an equally-readable dispatch table. All you need is for
your search terms to be hashable (which is a much less stringent
requirement than is usually put on switch blocks, like "must be
machine word signed integer"), and you can do something like this:

compare_key = {
    # Same target(s).
    ast.Assign: lambda node: ' '.join(dump(t) for t in node.targets),
    # Same target and same operator.
    ast.AugAssign: lambda node: dump(node.target) + dump(node.op) + "=",
    # A return statement is always compatible with another.
    ast.Return: lambda node: "(easy)",
    # Calling these never compatible is wrong. Calling them
    # always compatible will give lots of false positives.
    ast.Expr: lambda node: "(maybe)",
    # These ones are never compatible, so return some
    # object that's never equal to anything.
    ast.Import: lambda node: float("nan"),
    ast.ImportFrom: lambda node: float("nan"),
    ast.Pass: lambda node: float("nan"),
    ast.Raise: lambda node: float("nan"),
    ast.If: lambda node: float("nan"),
}

I then effectively do a big switch block like this:

if try_type not in compare_key:
    print("Unrecognized type",try_type.__name__,file=sys.stderr)
    compare_key[try_type] = lambda node: float("nan")
func = compare_key[try_type]
try_node = func(node.body[0])
for handler in node.handlers:
    if try_node != func(handler.body[0]): return

The first check (the "not in" bit) is kinda like a default clause, but
it makes the output only once for any given type. For a more 'true'
default clause, I could do this:

try:
    func = compare_key[try_type]
except KeyError:
    func = compare_key["default"]

but I take advantage of the fact that the dispatch table is a dict and
mutate it. Also, if this were done in a switch block, there'd be some
redundancy. I call the same function on node.body[0] and on
handler.body[0] for each handler in handlers, so there's structure
that's common to all the branches there. I'm not sure how, with a
classic C-style switch block, I could implement that cleanly. Probably
I'd end up using function pointers and basically doing it exactly the
same way :)

The only major thing C's switch does that a dispatch table doesn't is
fall-through. And let's face it, if your big argument in favour of a
switch statement is "I need fall-through", you're not just going to
have Python devs against you, you're also going to fight against the
roughly 50% of C programmers who detest that feature :)

(FWIW, I'm in the other 50%. I quite like fall-through, and there are
times when it's a very clean way to express something. But even those
cases can usually be expressed one way or another with only a little
more redundancy - for instance, have one case that sets the key to be
the next one, and then have stand-alone if blocks rather than if/elif.
Considering that that use of fall-through usually requires an
explanatory comment anyway, you're not really losing much.)

So, going back to your statement:

> Switch statements provide for excellent readability in parsers and state
> machines, for example.

The best way to start trying to build support for this would be to
mock up a syntax for a switch statement, and find a currently-existing
parser or state machine to translate. Show us the "before and after"
shots. That's what I'm currently doing to justify an exception
expression syntax - examples like this:

    pwd = (os.getcwd() except OSError: None)

    # Lib/tkinter/filedialog.py:210:
    try:
        pwd = os.getcwd()
    except OSError:
        pwd = None


    g = (grp.getgrnam(tarinfo.gname)[2] except KeyError: tarinfo.gid)
    u = (pwd.getpwnam(tarinfo.uname)[2] except KeyError: tarinfo.uid)

    # Lib/tarfile.py:2198:
    try:
        g = grp.getgrnam(tarinfo.gname)[2]
    except KeyError:
        g = tarinfo.gid
    try:
        u = pwd.getpwnam(tarinfo.uname)[2]
    except KeyError:
        u = tarinfo.uid


This is real Python code, straight out of the standard library. I
don't know if you could find many examples in the stdlib that beg for
a switch statement, but pick up some real-world code of your own, or
from some open source project, or something. Even if, in practice, it
wouldn't be changed for many years (because that project needs to be
compatible with previous versions of Python), it'd be worth showing
"here's what could be".

And then be prepared for a few hundred posts' worth of bikeshedding
about the details :)

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list

Reply via email to