On Sat, Jan 08, 2022 at 06:30:53PM -0800, Ethan Furman wrote:
On 1/8/22 5:46 PM, Steven D'Aprano wrote:
[...] if you hate type annotations because they are unreadable, then you hate Python because Python is unreadable.
Not so.
Are you disputing that annotations use the same syntax as other Python expressions? If not, I don't see how you can deny that "type annotations are unreadable" implies "Python expressions are unreadable", which in turn implies "Python is unreadable".
A simple list comprehension is (usually) quite readable, while a triply-nested list comprehension all on one line is not.
Indeed. We can abuse any syntax. So do we conclude that comprehensions are "unreadable" because we can write obfuscated triply-nested list comprehensions?
Similarly, adding type information in between a variable name and its value is not (for me, and apparently others too) readable.
I think that "unreadable" or "not readable" is a complaint that gets overused, often for the most trivial cases, to the point that it loses all credibility. Especially when it comes from people who are fluent in C (which may not be you, Ethan). http://unixwiz.net/techtips/reading-cdecl.html "Easily learned", huh. I think that this is one of the clearest examples of the curse of knowledge as it applies to programming that one could hope to find. Anyway, let's compare: # C int n = 44; # Pascal var n: integer; n := 44; # Typescript var n: number = 44; # Java int n = 44; # Python n: int = 44 There are millions who find the C, Pascal, TypeScript and Java perfectly readable. I don't find it credible that people are incapable of reading the last one. Aside: such a type hint is redundant, as mypy is perfectly capable of inferring that n = 44 makes n an int. Style guides should recommend against such redundancy, and I would certainly flag that in a code review. A better example of a *useful* type hint would be: L: list[str] = []
Most horribly of all, cluttering a function header with type information is most unreadable.
I hear you. Adding redundant or unnecessary type hints to parameters just for the sake of having type hints is just clutter, especially if they are never tested by actually running a type checker over the file. (Untested code is broken code. If not right now, it soon will be.) Fortunately, we have *gradual typing*, and nobody should be forced to use type hints in their projects if they don't want them. Just as we don't make linters mandatory, we don't make typing mandatory either. I think that, outside of very simple functions, once we make the decision to annotate a function, we should space them out: # Better def func(spam: list[str], eggs: float, cheese: str = 'cheddar', aardvark: str|bytes = "", eels: Optional[Tuple[int, str]] = None ) -> Hovercraft: which makes them much easier to read. Trying to cram them all into one line is abuse of the syntax every bit as bad as cramming a triply-nested list comp into one line: # Worse def func(spam: list[str], eggs: float, cheese: str = 'cheddar', aardvark: str|bytes = "", eels: Optional[Tuple[int, str]] = None) -> Hovercraft: I can read it, I just don't want to. It is too much like hard work compared to the previous layout. Even if you don't run a type-checker, those annotations can make useful documentation. (At least *sometimes*.) If the parameter name doesn't make it clear what types are allowed, then the annotation can make it clear. So if you don't use a static checker, you can think of type annotations as introspectable documentation.
I started using Python at 2.5. It was simple, clean, and elegant.
And I started using Python at 1.5, when the syntax was even simpler and cleaner. And to this day I will never forget the first time I read Python code, after being told repeatedly how easy to read it, and I couldn't make head or tails of it. All those colons and square brackets, it might as well have been APL. (Not that I knew what APL was back then.) I knew what a for-loop was, from Pascal, Hypertalk and HP RPN calculators: # Pascal for i := 0 to 10 do begin block; end; # Hypertalk repeat with i = 0 to 10 block end repeat # HP-48 RPN language 0 10 FOR I block NEXT but I kept seeing loops like this in Python: for i in range(11): or worse: for somename in [stuff, thing, another_thing, widget]: and worse of all: for somename in values[1:-1]: Python for loops looked nothing like any for loop I had seen before, and they freaked me out, and at the time (early 1990s) there was no publicly available internet where I could look anything up or ask for help. And then there were the brackets. Why does Python sometimes use round brackets, sometimes curly brackets, and sometimes square brackets? x[a] versus x(a)? Why were there sometimes colons inside square brackets and curly brackets {a:b} but never inside round brackets? What was the difference between [1, 2, 3] and (1, 2, 3)? The whole thing was intimidating, and I just put Python away for about a year and didn't look at it again until I had bought Mark Lutz' "Python Pocket Reference" which helped me make sense of it all. That and his "Learning Python". And never looked back. (Since then, I've often felt that Python has spoiled me from learning other languages. The point I am making here is not that I was a dimwit who couldn't even read Python, but that "easy to read" and "readable" is more a matter of familiarity than an inherent property of the language itself. With enough familiarity, even APL is easy to read.
If I had stumbled on it at 3.16 with samples, tutorials, and books all infused with typing clutter (which *looks* like boiler-plate even if it isn't) I wouldn't have given it a second glance.
And again, I hear you. I too wish people would tone down their enthusiasm for adding typing to examples that don't need type hints. We should remember that type hints are a feature aimed at large code bases, where static typing really is valuable. For three line example functions, not so much, not even as documentation. -- Steve