
Nick Coghlan wrote:
The named function version fails because it gets things out of order:
def sort_key(item): return item.attr1, item.attr2
sorted_list = sorted(original, key=sort_key)
That's more like pseudo code for "First, define a function that returns an object's attr1 and attr2 values. Than use that function to sort our list", a far cry from the originally requested operation.
I disagree strongly that the above "fails" in any sense, or that it is out of order at all. In English, one might say: Given such-and-such a definition of sort_key, sorted_list is calculated from sorted(original, key=sort_key). In my opinion, it is *much* more natural to state your premises (the givens) first, rather than after the conclusion. First you catch the rabbit, then you make it into stew, not the other way around. Furthermore, by encouraging (or forcing) the developer to define sort_key ahead of time, it encourages the developer to treat it seriously, as a real function, and document it and test it. If it's not tested, how do you know it does what you need? [...]
No, no, no - this focus on reusability is *exactly* the problem. It's why callback programming in Python sucks - we force people to treat one-shot functions as if they were reusable ones, *even when those functions aren't going to be reused in any other statement*.
That's not a bug, that's a feature. If you're not testing your callback, how do you know they do what you expect? Because they're so trivial that you can just "see" that they're correct? In that case, we have lambda, we don't need any new syntax.
That's the key realisation that I finally came to in understanding the appeal of multi-line lambdas (via Ruby's block syntax): functions actually have two purposes in life. The first is the way they're traditionally presented: as a way to structure algorithms into reusable chunks, so you don't have to repeat yourself. However, the second is to simply hand over a section of an algorithm to be executed by someone else. You don't *care* about reusability in those cases - you care about handing the code you're currently writing over to be executed by some other piece of code. Python only offers robust syntax for the first use case, which is always going to cause mental friction when you're only interested in the latter aspect.
You have missed at least two more critical purposes for functions: - Documentation: both adding documentation to functions, and self-documenting via the function name. There's little mystery of what function sort_key is *supposed* to do (although the name is a bit light on the details), while: :sorted_lost = sorted(original, key=@) return item.attr1, item.attr2 is a magic incantation that is indecipherable unless you know the secret. (I realise that the above may not be your preferred or final suggested syntax.) - And testing. If code isn't tested, you should assume it is buggy. In an ideal world, there should never be any such thing as code that's used once: it should always be used at least twice, once in the application and once in the test suite. I realise that in practice we often fall short of that ideal, but we don't need more syntax that *encourages* developers to fail to test non-trivial code blocks. -- Steven