On 2021-10-26 20:15, Chris Angelico wrote:
One truism of language design is that the simpler the language is (and
the easier to explain to a novice), the harder it is to actually use.
For instance, we don't *need* async/await, or generators, or list
comprehensions, or for loops, or any of those other tools for
processing partial data; all we really need is a class with the
appropriate state management. And there are people who genuinely
prefer coding a state machine to writing a generator function. No
problem! You're welcome to. But the language is richer for having
these tools, and we can more easily express our logic using them.

Each feature adds to the complexity of the language, but if they are
made as orthogonal as possible, they generally add linearly. But they
add exponentially to the expressiveness. Which ultimately means that
orthogonality is the greatest feature in language design; it allows
you to comprehend features one by one, and build up your mental
picture of the code in simple ways, while still having the full power
that it offers.

These are fascinating and great points, but again I see the issues slightly differently. I wouldn't agree that "the simpler the language the harder it is to use". That to me implies an equivalence with "the more complex the language the easier it is to use", which hopefully we agree is untrue.

Rather, both extremely simple AND extremely complex languages are difficult to use. The goal is a "sweet spot" in which you add complexity in just the right areas to achieve maximum expressibility with minimum cognitive load. And I think Python overall has done a good job of this, better than pretty much any other language I know of. And I think part of that good design has involved not "sweating the small stuff" in the sense of trying to add lots of special syntax to handle every mildly annoying pain point, but instead focusing on a relatively small number of well-chosen building blocks (iteration, for instance) and providing a clean combinatoric framework to facilitate the exponential expressiveness you describe.

As an example of orthogonality, Python's current argument defaults
don't care whether you're working with integers, strings, lists, or
anything else. They always behave the same way: using one specific
value (object) to be the value given if one is not provided. They're
also completely orthogonal with argument passing styles (positional
and keyword), and which ones are valid for which parameters. And
orthogonal again with type annotations and type comments. All these
features go in the 'def' statement - or 'lambda', which has access to
nearly all the same features (it can't have type comments, but
everything else works) - but you don't have to worry about exponential
complexity, because there's no conflicts between them.

One of my goals here is to ensure that the distinction between
early-bound and late-bound argument defaults is, again, orthogonal
with everything else. You should be able to change "x=None" to "x=>[]"
without having to wonder whether you're stopping yourself from adding
a type annotation in the future. This is why I'm strongly inclined to
syntaxes that adorn the equals sign, rather than those which put
tokens elsewhere (eg "@x=[]"), because it keeps the default part
self-contained.

So what I would say here is that argument-passing styles are in the sweet spot and argument binding time is not. One reason (as I mentioned in another post on this thread) is that argument-passing occurs at every call site, so simplifying it has the kind of exponential benefit you describe. But argument binding only happens once, when the function is defined, so its benefits scale with how many functions you write, not how many calls you make. Nonetheless two different argument-binding syntaxes still impose a cognitive burden, since now to be able to read Python code a person has to be able to understand both syntaxes instead of just one. The combination of "benefit scales only with the number of definitions" and "have to know two syntaxes all the time", for me, makes the cost/benefit ratio not pencil out.

There are also other issues, such as the difficulty of finding a good syntax. I think it's typical of Pythonic style to avoid cramming too much into too small a space. If we have a long expression, we can break it up into pieces assigned to separate variables. If we have a long function, we can break it up into separate functions. But there is no straightforward way to "break up" a single functions signature into multiple signatures (while still having just one function). This means that trying to introduce late-binding logic into the signature requires us to cram it into this uniquely restricted space. And this in turn means that a lot is riding on the choice of a syntax that is visually optimal, because there isn't going to be any way to "factor it out" into something more readable. (Other than, of course, the solution we currently have, which is to put the logic in the function body.)

Also there is the question of how this new orthogonal dimension relates to existing dimensions (i.e., is it "linearly independent"). Right now I think we have a pretty simple rule: If you need complicated logic to express as an early-bound default, you move that logic before the function, assign whatever you need to a variable, and then use that variable as the default. If on the other hand you need complicated logic to express a late-bound default, you pick some sentinel (often None) to act as an early-bound placeholder, and move the logic into the function body.

Now it's true that we have asymmetry, in that SIMPLE logic can be readably inlined as an early-bound default, whereas even simple logic cannot be inlined as a late-bound default because there is no inline way to express late-bound defaults. But I still think it's worth noticing that the new syntax is not going to add a "full dimension". It is only going to be useful for VERY SIMPLE late-bound default values. If the late-bound default is too complicated, inlining it will backfire and make things LESS readable. So although the syntax is orthogonal, it is not really separating out early and late default binding; it is only separating "simple late default binding".

Finally, even if the change is orthogonal to existing dimensions of function-calling, there is still a cost to adding this new dimension in the first place (i.e., one more thing to know). And there still needs to be a decision made about whether the cognitive burden of adding that dimension is worth the gain in expressiveness. Of course there is a lot of subjectivity here and it seems I am in the minority, but to me the ability to concisely express short, simple expressions for late-binding defaults doesn't fall in that sweet spot.

--
Brendan Barnwell
"Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail."
   --author unknown
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/LVQCYHZE6FMZK5TPT74RX5LWP7BQEF6H/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to