Permission and Forgiveness

2006-12-07, , Comments

I needed to check the times for trains from Bristol to London, so I went to http://thetrainline.com and filled out the Quick Timetable form, entering bristol temple meads and paddington as my departure and destination points.

Which Paddington?

As you can see, I stick to lowercase when I’m in a hurry — but I did remember to specify which of the Bristol mainline stations I intended to leave from, knowing from previous experience that the Quick Timetable wouldn’t be smart enough to simultaneously provide answers for both.

Unfortunately the trainline software couldn’t figure out which Paddington I wanted to travel to. What’s worse, it tried to cover up its confusion with a plethora of information and options. Sure, a single click on the LONDON PADDINGTON link was all I needed to do, but that link was buried half-way down the page, after a text entry field and 26 alphabetic selection links.

The trainline station finder

As you can see, the trainline uses uppercase for stations, which isn’t reader-friendly.

London Paddington has to be the best known station in the country. It’s also the only mainline Paddington station. It’s also in exactly the same place as Paddington Underground. Couldn’t the Trainline have made a wild guess that when I said Paddington I meant LONDON PADDINGTON?

By this stage, I was curious to find out what would happen if I asked for train times from Bristol Temple Meads to Paddington Underground. I got a page full of unhelpful apologies, starting with:

We cannot find any services that meet your request. Possible reasons and solutions are listed below. You can try and search for another journey by clicking on the back button:

State of the Art

The trainline isn’t an egregious website. As you can see, I prefer it to National Rail Enquiries for timetable queries. But that’s no excuse. The trainline should have figured out which Paddington I meant without needing my permission to do so and begged my forgiveness if it misinterpreted me.

In the same way a good email client should go ahead and delete an email when I ask it to, rather than present an irritating confirmation dialogue asking if I really am really sure I really want to really delete it. I end up OK’ing such a confirmation dialogue on auto-pilot, meaning that the one time it might save me, it doesn’t. A good email client just deletes the message without further permission and provides a way for me to recover it if necessary.

It’s sometimes worth reminding ourselves that computers are here to serve us. As programmers, perhaps we’re rather too used to wrestling them into submission – perhaps we sometimes secretly enjoy it – and as a consequence we expect other users to tolerate such impudence.

Being a programmer, of course I managed to get the train times. I then found myself thinking about a common Python idiom for handling tricky inputs gracefully.

Permission and Forgiveness

Code written in a statically typed language — even if that language supports exceptions — often gets swamped by error handling code. There are more ways for things to go wrong than right, and they all need handling.

Python is a dynamic language. Doesn’t that mean that there are even more ways to go wrong? After all, the compiler isn’t going to check that we pass a nice string into the following integer conversion function.

bad number converter
def string_to_int(number_string):
    from string import digits
    char_to_digit = dict(zip(digits, range(10)))
    number = 0
    for ch in number_string:
        number *= 10
        number += char_to_digit[ch]
    return number

This function is woefully under-specified and guaranteed to misfire in the face of poor input, but with a little more thought it can go right in situations which it wasn’t originally coded for. Here’s an improved version:

better number converter
def thing_to_int(thing, default=0):
    """ Converts the input thing into an integer, returning a
    default value if the conversion fails.

>>> thing_to_int("10")
    10
    >>> assert thing_to_int(1.e12) == 1000000000000
    >>> thing_to_int("one")
    0
    >>> thing_to_int("one", default=-1)
    -1
    >>> class forty_two(object):
    ...     def __int__(self): return 42
    >>> assert thing_to_int(forty_two()) == 42
    """
    try:
        return int(thing)
    except:
        return default

def _test():
    import doctest
    doctest.testmod()

if __name__ == "__main__":
    _test()

The function’s documentation shows just how flexible this simple function is. The idiom used here is to proceed assuming that the function caller has passed a suitable thing parameter, but to handle any errors which occur.

Here’s another example, showing how we can use set in a version of Python which didn’t build support for sets into the core language. Note that we don’t attempt to query version numbers or check which libraries have been installed. We just try and use set and handle any fall-out.

Using set in old and new versions of Python
try:
    set
except NameError:
    from sets import Set as set

This idiom is known as “it’s easier to ask forgiveness than permission”.