[Python-ideas] Does jargon make learning more difficult?
Abe Dillon
abedillon at gmail.com
Wed Aug 22 13:11:40 EDT 2018
[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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180822/7ab16386/attachment.html>
More information about the Python-ideas
mailing list