Untyped Python: The Python That Was
lucumr.pocoo.org
lucumr.pocoo.org
I think what changed is the static languages got a lot better. Static languages when Python was invented were awful. Now I can write very sensible code in static languages that looks almost as nice as dynamic code, while still carrying all the benefits of dynamic languages. That's what flipped the cost/benefit tradeoff for me; yeah, I'm personally a bit grumpy about Python just getting more and more complex, but if it was 2023 Python versus early 2000s static languages I'd still be using Python.
But that's not the choice I face.
But then I have the type system, which saves my ass every day. Maybe I am just an old dog who can't memorize complex code bases like in my youth. The good thing is that I no longer need to do that.
(Edited from “was invented” to “got really popular”… python was invented before Java or C#… Type inference in these major language didn’t happen until circa 2010 or later.)
https://en.wikipedia.org/wiki/ML_(programming_language)
Significantly before Haskell, and certainly worth looking at.
I'm not sure why it lost in the popularity wars. Maybe it lost to C/Pascal/C++ because it had garbage collection and then never regained its footing.
I do not mean this as some sort of major criticism or anything, just as a brute engineering fact. It's not like Haskell has a huge suite of programs I can point at that people use because they are legitimately useful and not because they are Haskell programmers (Pandoc, XMonad, and honestly I'm already coming up a bit dry here), but I can still point to more than I can ML, which is a flat zero.
I guess we could debate what "good" means...
There was no option in the early 2000s of deploying any significant production software in ML. You can see this very easily, let me make a list: C++, C, Java, ML. Which one doesn't belong to the set "production-quality language in 2001"? Anyone who can't answer that is just being deliberately obtuse. Even though Java was jammed into that list more by executive fiat than anything else, it was still in that list. You may like ML a lot, you may have preferred to work in it, but it is very clearly not able to run in the same spaces as C++/C/Java.
I used Haskell circa 2012 to build a highly concurrent network server for a game contest. I'm glad it was just a game contest and the biggest stakes were which college student got the grand prize. It crashed under load. Really quite a bit; I was restarting it about every hour, under what would have been a trivial amount of load by any production standard. And it was just Haskell bugs; as Haskell advanced, the exact same code crashed less and less. Probably it would be fine today. But this is the sort of thing I mean; even if you like the language, production shines a really, really harsh light on a language. Having a theoretically superior concurrency solution didn't make my code not crash the runtime. Having a theoretically superior typing solution still leaves a language needing tested libraries, tested framework, exposure to real world situations. The odds of hitting a critical snag is just too high otherwise.
Even today, telling me that you're building a critical system in ML would basically be a firing offense (if you obstinately stuck by it). I'm as open minded about languages as anybody, but I know enough to know with close-enough-for-business-certainty that it's a bad business decision. Should the ML community suddenly revive and fix up the language over the next 5-10 years, I'd happily reconsider, but today? No question. Even Haskell would be a very uphill battle, for many good and sufficient business reasons, but maybe you could just about convince me there's some task you just need it for.
(start here to not get bored) Haskell wasn't definitely ready for enterprise. It felt very researchy.
I do remember other functional languages with HM-style types that felt more polished and ready: e.g. I did learn Ocaml, or Concurrent Clean.
https://en.wikipedia.org/wiki/Clean_(programming_language)
Jane Street adopted Ocaml in the mid 2000s.
https://blog.janestreet.com/what-the-interns-have-wrought-20...
"Yaron Minsky joined Jane Street back in 2002, and claims the dubious honor of having convinced the firm to start using OCaml."
https://www.janestreet.com/tech-talks/jane-and-compiler/
"that’s when we made a real kind of fateful step into integrating OCaml into our permanent infrastructure. And that was like in 2005, we started doing this 2006, we had about 10 people who were programming in OCaml"
(more off-topic) [1] I really liked Ocaml... but, sadly, I discovered it only after I finished that sales agents system and the final version was in Delphi for the main app and Dolphin Smalltalk for the sync part (I needed metaprogramming to pull it off).
[1] I think that's what was popular in the Java world at the time? I'm not sure on this.
> ...after I deployed bad code and it blew up in someone's face, I got an email that not only shows a perfectly readable stack trace, but also a line of source code for the frames.
This is an odd contradiction. On one hand, he values the fast feedback loop you get from edit-and-reload style development, but on the other, he doesn't appreciate the instant feedback from the type checker saying it's not gonna work.
If you want "quick feedback loops", a type checker is probably one of the quickest. Almost always quicker to give feedback than tests even. And certainly quicker than ftping code to a webserver and clicking through a webapp manually.
I was initially not very convinced of the benefits - why have a type-checker if you won't use it in the small? But IDEs provide much of the benefit inline while writing code and this is seldom a real problem from the DX perspective.
My skepticism was unwarranted.
Says who? Or is it: we're way past the toy stage of web apps with their trivial CRUD forms, so the code base grows, complexity is unavoidable, and untyped languages are a bad choice under those conditions?
> Java did not even have generics.
Java got generics in 2004 (when the author was 15?).
> The core principles of [using typing] have not altered: types add value and they add cost.
And that's the conclusion? I'm flabbergasted.
But yea I also don’t know why you’re responding so strongly. He basically admitted he didn’t have a strong opinion for either side, and was mostly musing on a vibe that seems to have moved on.
He’s mostly developing in rust these days, so I’m sure he knows the value in good typing.
Lastly, anecdotally, I’d agree with his observation about trusting programmers less. There is definitely a safety-ism culture. Probably from massive professionalization of the web dev space. Again, anecdote.
1. https://www.quora.com/Armin-Ronacher-How-did-you-start-out-p...
If you believe type hints in Python are a net negative, then just don't use them.
I also don't get the performance argument: Type hints aren't evaluated at runtime, so whether code has them or not shouldn't matter for performance.
> I also don't get the performance argument: Type hints aren't evaluated at runtime, so whether code has them or not shouldn't matter for performance.
Type hints actually are evaluated at runtime, since they're part of the syntax tree. They just aren't checked for correctness, so the (small, but sometimes non-negligible) amount of parsing overhead they add is essentially lost performance.
The correct way would be to do conditional imports that are only performed while typechecking in this case. But I'd expect the issue to happen often.
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import mymodule
def myfunc(arg: mymodule.MyType) -> bool:
return bool(arg)
This will make type annotations to be converted internally to strings. TYPE_CHECKING is a constant that's always false except when you use a type checker, so mymodule will never be imported on runtime, but the type checker will think that it was imported. from __future__ import annotations
This will break all the runtime type checking modules (such as typedload, which I wrote).Most runtime type checking modules I am aware of support
from __future__ import annotations
E.g., typeguard, Pydantic, beartype, among others.When I had complained earlier, Guido didn't give 2 shits, although it broke typedload, a library I wrote that is very similar to pydantic, when pydantic didn't exist.
So I know what I am saying
I hate this spin to somehow make it about not trusting developers to do their job right. As if it was an insult to test for correctness. Imagine skipping pre-flight checks for accomplished pilots, or load calculations for renowned bridge engineers.
This hubris of some software developers assuming to be hyper-correct geniuses really alienates me.
I've made this comment or something like it about other languages and maybe Python in the past:
There is a fundamental conflict between simple languages and "enterprise" languages. By "enterprise" I mean languages used for large-scale projects by large teams-of-teams who need to integrate work without having (or not easily and readily having) direct access to each other. Once you start trying to work on a large system or collaborate but via specs, the game changes.
Python started as a teaching language, a next-gen version of ABC. It was meant to be simple and accessible for learners (see also BASIC, Pascal). Once it started getting used in production, users started hitting walls and early design decisions were creating problems. The language was extended and altered (see also variations of BASIC, Pascal) to better support these new use-cases while also trying to maintain the simplicity of the original. That got us Python 2.0 and later Python 3.0.
To some extent you can maintain simplicity while adding new capabilities if they are optional (or used by the simpler interfaces so the users don't see them if they don't want to). Like having all the dunder methods or even decorators (creating your own). The type annotations are similar. They're a useful feature, and totally optional for the vast majority of users. But they're useful for the case of people going beyond small teams and small programs. They're useful for scaling up and integrating with other efforts because they provide another point of shared communication about what is expected from the program's interfaces.
But yes, Python becoming more like Java should be expected. For all the flak it gets, a lot of what went into designing Java was targeted at scaling the development effort. If you want to scale, you will end up imitating some aspects of Java, Ada, and other languages designed (initially or over revisions) for that purpose.
Maybe they should have pulled a Wirth. Changed the name so that instead of Python 2 (and 3) we had a renamed language built on many of the same concepts and syntax. But they didn't, and it's no use crying now.
I have a different hypothesis on this (never checked it though). I think as those languages - Python, JS were getting more and more popular we started using them more and more to build and maintain more complex projects. What worked for a few glue-code scripts doesn't work for a Dropbox size and scale project. Thus there was a need to evolve and augment the language.
One thing that always bugged me was the lack of "strict" mode (see perl)
For those of you not blessed enough to have done any serious perl, strict was a way of making sure that you hadn't accidentally assigned an output to a spelling mistake. In other words you had to declare all your variables _before_ you used them.
I got used to the lack of it, and grew to rely on pylint and other tools.
Then I went back to C++ (fuck that as ever) and started c#.
I really like c#, and I finally realised what I thought was missing from python: semi static typing.
Sure, you can use type hints, but they are pretty much useless as they have no runtime effect, so are basically highly schema'd comments.
What I would love is a mode in python where instead of duck typing everything, its statically typed, unless I do a magical incantation.
I know that will never happen, as its against python's core values. Its also entirely no trivial to re-build the interpreter. But it would mean we can stop type checking all the time & catch a fucktonne of bugs early.
What Perl has as "strict" mode, Python just has by default all the time. The typing is dynamic but strong, it will complain about unbound variables instead of silently creating them, scoping, while not ideal, is sane out of the box.
What Python doesn't have is taint mode, but that is quirky and fragile as hell in Perl as well, and more of a nuisance nowadays than an actual aid.
Are you sure about that?
def example(x):
result = 123
if x == 456:
reslut = 789 # undetected typo
return result
In Perl, this throws an error at compile time: use 5.036;
sub example ($x) {
my $result = 123;
if ($x == 456) {
$reslut = 789; # <-- Global symbol "$reslut" requires explicit package name
}
return $result;
}You don’t need this baked into the language.
I think all popular linters for Python catch the mistake you have shown since a decade or so. You create a variable and then don't use it.
On the other hand, I've been screwed over by wild list context suddenly appearing and swallowing my undefs (that I needed to be there) countless times. To this, Perl doesn't have a remedy. It won't even warn me that a function returned an undef but the list context squashed it into nothingness. This causes, for example, off-by-one errors that lead to creation of malformed hashes which sometimes may look almost right except the data is corrupted now. Even worse, two undefs instead of one will silence the warning about odd number of elements forming a hash, and that hash will be just as corrupted.
Perl weenies very much like to brag about crutches their language invented to bravely overcome the problems it itself has created, and it prevents them from realizing how limited those crutches are.
They way function args are define is just pure passive aggression (oh so you _want_ people to name args properly eh? go on, you validate the arg name. Go on, fiddle with the hash like that). However we should be able to look at any language and pull out its interesting/useful features.
I loved perl's can do attitude. You wanna do that to an array? sure. it'd going to end bad, but here you go kid, have at it.
Is that good for everyone? no.
does it mean that you need to be hilariously disciplined to make readable code? very yes.
Can one make readable and maintainable code in perl? hell yes. I've worked on some wonderfully maintainable perl code.
I've also worked on a lot of shite. But then the same goes for python. I transmute research code into production, and let me tell you, there are some "perls" of programmers right there.
But saying that Python "will complain about unbound variables instead of silently creating them" is simply incorrect. One of the defining characteristics of Python's variable scope is that assigning to a name always creates it in the current scope; there's no other way to declare a variable. And that's all I'm trying to demonstrate here.
Perl in non-strict mode, PHP last I touched it, Bourne-style shells will just go “yeah, whatever, here’s an empty string”.
Moreover, people who have dyslexia are not always able to spot stuff like this.
> What I would love is a mode in python where instead of duck typing everything, its statically typed, unless I do a magical incantation.
You can do this with mypy, you can set it to require types everywhere.
But it don't help with stuff that gets transmuted in flight. Granted with pydactic is your friend here, but thats a lot of work to backport.
basically I want c#'s typing system with python's syntax.
It's a common trope: https://en.wikipedia.org/wiki/Worse_is_better
Given that in 2023 the go-to language for serious server applications is still Java (at least by plurality), it is not at all surprising that languages that wished to displace it but didn't (or haven't) would eventually become more like it if they want to play in that domain.
Also another comment about the apparent contradictions about moving quickly without types and then dealing with runtime errors. I don’t think the author actually said anything about the nature of the stack trace, and I’m pretty sure lots of typed languages have runtime errors that require combing through stack traces. The point of removing the guardrails is that it allows you to shape the clay that is your code, iteratively, without thinking too hard about the correctness. “Yes it isn’t right, but it’s good enough for 70% and I’ll figure out the rest after I prove this out.” When you have “grand” ideas you want to be able to get it out quickly, and not be slowed down by proof. Is it really that hard to keep those two ideas beside each other?
I wonder how much money was at stake from these edits?
Sorry for another hot take. I definitely have sympathies toward the author's position, we are giving up flexibility which is Python's main strength for what exactly? (Might as well write Go/etc if you want static types)
- the good: as a reader I love not having to go to call site after call site figuring out _what type_ of thing was passed in and how that happened (google docstring projects don't have this problem but open source often does)
- the bad: as a writer the types are complicated and obtuse, and the more fancy first order stuff you do the uglier the types get (and just forget type annotating your unit tests). Also mypy is slow
- the ugly: the sadness of putting in all of the typing and it _not_ but used to improve runtime performance. Commonlisp gets something like a 30x speed improvement for type annotations because native code can be used in place of lisp objects, why can't we have _that_? I would feel much better about Python types if we did that
What I think has changed instead: Python is used for large code bases, unlike 15 years ago, and without type annotations it is an unreadable mess.
So what exactly is the complaint?
The worst thing for me is I have to work with javascript devs that can also do python and they incessantly use python type hinting because they come from typescript.
It's useless, clutters up the code and decreases readability.
It's all sunshine and rainbows if you work with well documented libraries, but there are tons of python libs out there that just use *kwargs as their function signatures with maybe - if you're lucky - a list of actual keywords (no types or description though).
Those are hell to work with, because the IDE can't tell you what's going on. So you either have to delve into sample code (and hope that's still up-to-date) or look at the source code to figure out what the heck is going on.
So no, I personally don't find type hints useless at all - especially for libraries. Readability OTOH is subjective and mostly just a matter of what you're used to, anyway.
I submit that dataclasses refute these claims handily.
To people that like what they see in this post please try Go if you are on a team. TypeScripts attracts typing astronauts that can and will turn a codebase into and unmaintainable mess just the same as untyped Python.
Of course you might not value team velocity over your own velocity and that, is indeed, a different conversation.
I have said for years: Go makes teams effective the same way Python makes individual devs effective.
Separately:
> Companies like Microsoft did not even think that Python was a language yet
So tired of this trope. Microsoft shipped Python in 1996[0] AND they invested in the ecosystem by offering IronPython[1].
0: http://python-history.blogspot.com/2009/01/microsoft-ships-p...