I detest the Python static-typing annotation schemes

Kenzaburo Gell-Manti-Te’o
9 min readMar 4, 2020

--

Basically. I don’t hate Python type annotations, per se — there is nothing wrong with thing: int = 0 in my eyes. But those same eyes do bleed a little whenever I have to look through any given morass of verbose flotsam of the sort the typing module encourages people to write.

Here’s a list of my basic grievances.

  • The typing module, and its bestie the MyPy static analyzer and linter, have preëmptively hijacked two plausibly useful new dunderized Python classmethods: __class_getitem__(…) and __mro_entries__(…), which according to their introductory PEP are off-limits for uses beyond the implementation of the typing internals (q.v. section end-note sub). Which OH COME ON. Abusing dunder methods is what makes Python Python. How else are we to be explicit, which is better than implicit? Didn’t Python only recently add __matmul__(…) and friends, which don’t even have a default implementation like anywhere in the standard library?? Ugh. When people do shit like subpath = pathlike(¬) / pathlike(•) / "frag.ext" should their “abuse” of the __truediv__(…) and __rtruediv__(…) dunders be slapped with some kind of new MethodOffLimitsError?!
  • Speaking of MyPy, I worry that its development is driving the design of thetyping syntax and semantics, and it’s at version 0.70.something. I will be blunt: I don’t like MyPy. I don’t like its premise — a programs’ emergent typology is intrinsic to the nature of its functionality; dealing with it using a linting phase — the same way you deal with malformatted docstrings or excess line breaks around method defs — is sending the wrong message.
  • I also don’t like how MyPy behaves at the moment. I have a project that has some basic definitions in a few Python modules: ABCs, boolean predicates, stringification utilities, &c &c. Those are then used to build a loose hierarchy of data structures, and then those are used to build some interoperable (and hopefully extensible) basic apps. So it’s like a cheeseburger, in terms of how many layers there are and how those layers build and utilize one another. Right? So getting the imports right at that low level is important, to avoid circular dependencies and priority inversions and stuff like that. Well, so. Since MyPy is a static analyzer, it has to represent the target Python program without actually executing it. In some areas, it does this perfectly smoothly — Python’s standard library offers APIs for (say) working with intermediate abstract-syntax graphs, and the interpreter’s internal structures. Some stuff is rather rough, though, and specifically the way MyPy analyzes imports needs to be shot at dawn and reimplemented from nothing. Last time I checked, it did not differentiate between import statements at module-level and those enclosed within function and method defs, which is wack. It also had no idea what to do with imports that didn’t refer to straight-up super-vanilla standard, ordinary file-based code modules — considering how much API churn there has been around getting the sys.meta_path import hooks juuuuuust right, you’d think someone from the MyPy cabal would have noticed that this shit kind-of matters, n’est ce pas?
  • The types and annotations on offer from typing add a shitload of crufty visual noise to the code.
Yeccch
Figure 1: Yeccch.

… This is something I wrote last year, when I was doing import typing as tx to try and mitigate the avalanche of absurd scarecrow types one has to pull out of typing. I particularly bristle at the __slots__ member’s requisite annotation (theClassVar[Tuple[str, ...]] bit) as it is made unnecessarily longer by a) requiring that ClassVar annotations enclose the type hints for a class member and b) using the ... tag in the Tuple hint to indicate that it’s a homogenous invariant. Fuck me. How about just:

__slots__: tx.MonoTup[str] = tuple()

…? Or better yet:

__slots__: tuple.mono[str] = tuple()

… that’s actually a little weird, maybe not. Hrm. No judgement in brainstorming. I wouldn’t even mind maybe something along the lines of:

__slots__: tuple<str, …> = tuple()

… more on the meaning of the angle-bracket syntax to come. But yeah, yeah! We’ve got Unicode support for identifiers in the interpreter, let’s use it! Alias Ellipses to a higher point of code!!! Yes.This example has digressed towards absurdity, but you see what I am saying.

But so yeah OK, now for the big one:

  • The typing module relies on a parallel hierarchy of dummy types to both represent built-ins and to disambiguate certain syntactic nuances.

… like, why? Like I said, doing thing: int = 0 is fine, but:

from typing import Any, ClassVar, DefaultDict, Optional, TypeVar

and then

thing: Optional[DefaultDict[str, Any]] = defaultdict(lambda: None)

instead of just like

thing = defaultdict(lambda: None)

… is a bit fucking ridiculous. Importing all of those fake names is coarse, and vulgar. Unpythonic, even. And look, first of all: the very authors of all of this stuff have acknowledged this fact, more or less — to the point of drafting a PEP explaining this particular logorrheic fuckedupedness, and proposing to fix it by depreciating all of the duplicate dummy types in typing (so typing.Dict and typing.List and typing.Str, &c) and making the actual builtins work with “type parameters”.

I put that last bit in quotes because, secondly, the whole thing of using the subscript operator (née ‘brackets’, or […]) on a typename is also something I consider rather grossitating.

By dint of some explanation, allow me to stop for a moment and give you a bearing or two through the dense fog of my personal opinion: I fucking love C++. I totally fucking love it to pieces. I realize this is a minority opinion; most people with significant domain-experience can expertly enumerate a shitzillion reasons why C++ is a loathsome shambolic chimerical plague on the programming zeitgeist; I have read their amusing posts whilst myself trying to wrap my head around some C++ curve thrown unexpectedly my way on one long late night or another.

Yes. OK. But:

See yeah that last bit, about the Zen state of it — that, and how C++ isn’t really object-oriented so much as it is struct-oriented — those are the foundations of this love I have for the language. Which, I should probably note at this point, I did not fully immersively learn until 2012, which is after 2011, which is when C++11 happened, which was (in case you were not as of yet aware) a big deal.

But so yeah, the language that I generally hate the most — the one I personally can systematically and righteously discredit and defame at the drop of a conversational hat — is Java. I earned my right to loathe Java. I programmed n-tier web service architectures against the first motherfucking commerically available Servlet container ever (JRun 1.0 dogg) and then waded through the horrible mire of JSP; I wrote several ORMs and even an FRM (all of which were pretty fly-by-night, but they worked); I wasted upwards of six to eight hours trying to debug something that was surely dark and formless and as evil as anything that ever walked the Earth — only to eventually discover that a few mislaid characters in the $CLASSPATH were the cause of the crisis. I used JBoss. I used WebSphere.

In recent years my piss and vinegar has evaporated, and so I am able to refrain from telling e.g. total strangers I meet at the bar why anyone subclassing java.lang.ClassLoader is a spiritually-impoverished sociopath in need of a good public flagellation — honestly, I am no longer that person. I think I was that person, like, back in my twenties; I personally apologize if you knew me then and had to listen to my effusive nonsense on this kind of subject.

But yeah so, the point. OK. The point is that, within this battle-tested and time-tempered taxonomic distaste I harbor, I think I have the largest untempered chunk of raw anti-Java ire remaining for the Java languages’ “generics”.

For a while, Java had no generics. This wasn’t too big a deal; everything under its hood was pointers and everything internally was mercilessly aligned to the system word length (I think?) — also, since “Java” was at once the language, the runtime, the virtual-machine execution environment, and the standard class library, it could count on (just about) every object instances’ class descending from one singular root Object class.

This last fact makes casting things in Java much different than in (say) C++. Internally, the Java runtime can alter the type-signature metadata records for the castee, which is of a piece with a s/typename/castname/g type of thing.

JVM type signature composition.
Figure 2a: JVM type signature composition.

Contrastingly, in C++ casting and conversion is a whole doctoral dissertation of extremely specific, prickly rules and regulations. So it does not matter whether you find these stipulations irritating or (in my case) a long-overdue salve of logic and order, somehow palliative against the endless obsessive-compulsive reflux churn of ones’ own thoughts… we can all agree that casting in Java is pretty thoroughly different from casting in C++.

But like so many other decisions made by the people in charge of Java, they added “generics” to the language in a way that looked like something they knew, regardless of the way it acted. Java generics work like up- and down-casts within an object hierarchy with a single root; the closest thing C++ has to something that looks like this is dynamic_cast<…>(¬) — but C++ type metadata is barely even a runtime thing, they don’t act the same at all. A cast in C++ will either reinterpret memory or invoke a type conversion operator, or possibly both. Downcasting an instance (even through a straightforward, unbranching type tower) can “slice” the object in memory if the downcasted instance is a temporary passed by value.

Just one of those crazy nuanced C++ rules & regulations
Figure 2b: “A polymorphic class should suppress copying” is not a thing in Java. However, it is very much so in C++.

I digress, don’t I? This really is all to say that I don’t like what they did in Java — which was: they made generics look sort of like C++ templates, even though they do not act like them. They used a similar angle-bracket syntax for “specialization” and declaration of generic types. But then found that they had to add a bunch of weird shit that doesn’t immediately make sense — like the question-mark typename wildcard-ish expressions — which lead to “PECS” being something you need to remember while you’re attempting to do all this generic Java-ing.

But it’s all bullshit, isn’t it? I don’t know thing one about javac and how it optimizes things, or the many many many extravagant systems upon subsystems within the JVM that all strive for the most flawless execution of your code. But I do know that Java generics use “type erasure” — genericized expressions literally do not give a proper fuck about types, meaning all of that metadata the JVM is looking after is just ignored, like when you do, say:

Object object = (Object)derivedInstance;

Figure 3. Advice from the experts.

Experts agree: this isn’t faster, or better. Java generics look like C++ templates, an arcane feature one can use to forge a path to the optimal — but they act like a big shoulder-shrug of a dynamic downcast.

So yeah — those are my feelings. Now you know them. Strangely enough, writing this up has made me want to actually go and program in Java again — if for no other reason than to maintain the experiential bonafides that permit me to fucking hate it. But also, you know, when something bothers you, it’s often quite instructive to take a good hard long look at that thing, and to see exactly what it is that is doing the bothering, and how it might work. Yes.

--

--

Kenzaburo Gell-Manti-Te’o

Tips From The Best Homeless Objective-C++ Programmer On Earth – With Homeless Experience In New York, Los Angeles, and Baltimore