[Steven D'Aprano] > > The revelation that it's a function should come when you read the "by" or > > "key". > I disagree. The most important fact is that it is a function, not > specifically what it does.
I was trying to say that the context almost always gives away that the reader should expect a function. Again, the pseudo code: hand = sorted(cards, by=card.suit) Is usually enough for most people to understand. When you add in what the computer needs to make the whole thing mechanically unambiguous: hand = sorted(cards, key=lambda card: card.suit) You can see the noise added compared to the pseudo code. The difference (to me) looks like this: hand = sorted(cards, by#=#############card.suit) Ideally, we could move that noise out of the way so that the intent is more clearly expressed: hand = sorted(cards, by=card.suit ########## Functions, variables and parameters are normally named such that they give away lots of context (or they should be). Context that's available to the reader but not the computer. [Steven D'Aprano] > Consider: > widget.register(value[a](x) with x) > At first it looks like you are evaluating value[1](x) eagerly, right > there in the method call, and then you have to backtrack and change your > expectation about what you just read when you get to the end and see the > declarations. First, how is your example any worse that the delayed binding of generator expressions? widget.register(value[a](x) for x in things) Of course the context of what they're reading builds as they read it. You could put those spaces anywhere: widget.register(value[a] (x) for x in things) Delayed binding works because human readers can deal with a little ambiguity in what they're reading especially if it means putting the intent of the code before the book keeping. If we're assuming a naive reader who's never seen the widget.register method and method and variable names that are pretty ambiguous (not unheard of, especially in anonymous functions) then I would say the blocker is not knowing what widget.register is in the first place. If you don't know that, then what point is continuing to read? Will knowing the kinds of object being passed clear everything up? widget.combobulate(5, "elephant", True) Once the reader has encountered widget.register they'll know it takes a function as an input. Just like anyone who's used time.sleep will be thrown for a loop as soon as they see lambda in: time.sleep(lambda x: value[a](x)) [Steven D'Aprano] > Given some sort of look-ahead, it's *possible* to put the parameter list > at the end of the function body: > def function: > do_this(a) > do_that(b) > do_something_else(c) > with (a, b, c=None) > but I think you can see why that would be annoying. Yes! Common ground! <doing my common ground dance> Named functions serve a different purpose than anonymous functions. They usually handle situations where the interface is more important than the implementation. def square_root(x): ... Better spit out the square root of whatever number I give it. I don't care how. Anonymous functions are almost always used in contexts where the interface is implied and the important bit is *what* it does: ui_element.on_mouseover(<what to do> with event) [Steven D'Aprano] > You don't even know > which names are global and which are parameters until you get to the > very end of the function. Blah. > The same applies to function expressions. Given the function body: > value[a](x) > which of value, a and x are global names and which are parameters? The same could be said of the value[a](x) in: widget.register(value[a](x) for x in things) [Steven D'Aprano] > "Wait wait wait!" should ideally never happen. In programming, surprises > are not a good thing, and they're even less good when they are > retroactive. It happens when someone has never seen the map function used or sorted used with a key function. It happens during the time where reading code is hard to begin with. The "wait wait wait" happens when you don't know what map is. Once you learn that, then you learn that the first parameter is a function. Then whenever you see map, you should expect the first parameter to be a function. No amount of broadcasting that the first parameter is indeed a function will cure the confusion of not knowing what map, or wiget.register is. *Having said all that:* Jonathan Fine pointed out that the <EXPRESSION> <SEPARATOR> <SIGNATURE> format that I've been championing (where I've been using "with" for the separator) has a subtle flaw where empty signatures are pretty awkward: d = defaultdict(100 with) It *kind-of* works with "def" and you read it as, "the preceding expression is deferred" followed by an *optional* signature declaration. d = defaultdict(100 def) But it's not great. Another alternative is to use some (ideally short) prefix and making the signature declaration optional: <PREFIX> <EXPRESSION> [<SEPARATOR> <SIGNATURE>] d = defaultdict(def 100) hand = sorted(cards, by=def card.suit with card) of course there are many possible choices for the prefix and separator and subtle alterations like: hand = sorted(cards, by==>card.suit with(card)) . # where '=>' is the prefix, it just blends nicely or: d = defaultdict(100 with()) Anyway, none of that interests me. What interests me is the recognition of *why* expressionization of statements often leads to a re-ordered version of the original. My thesis is that expressions are slightly more flexible than statements which allows for a more expressive ordering that is sometimes better for readability. I think it's important to understand that if we ever expressionize other statements like try-except, with, etc.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/