Another big benefit is how it makes your code self-documenting -- and makes that self-documentation self-enforcing. If you are making an API for consumption, it's dead simple to make it very clear how it's structured, and to set & enforce contracts for its usage.
For me, in general it is more about its power to ease the cognitive load associated with coding. Having to constantly look up documentation to see what the types of arguments are and in what order they need passed brings me out of flow. When I don't have to think about that, I can spend more time focused on the problem at hand. That doesn't mean I can't program without them; I have to do a fair amount of Vue stuff at work and I make it work despite not being a front end guy. But my preference is always for static typing.
Over time, this has a very noticable impact on development speed, and in a very painful way for the individual developers who need to spending your time tracing the various call paths in unrelated code and build up a mental model of the involved types every time they want to do a nontrivial change, and then deal with the fallout of unintentional bugs due to missed code paths for an unpredictable amount of time as they are discovered.
So when there is a solution that can completely eliminate this class of bugs in an automated way, this sounds like a very appealing feature.
It's basically like they work by accident. It's not documented what goes in or goes out, so someone throws some data into a function and if it doesn't break then consider it success. And any future person that has to make a change has to untangle that whole mess.
How do you know what the argument “details” should be?
The function documentation says “takes the query details” ... so people sit down in the repl and just screw around with the functions to try to figure out what they do, or search for examples of how they’re used.
with type hints, this discovery process for other programmers was significantly quicker and on boarding was faster.
I mean, sure, just write better docstrings, but type hints can’t get out of date, because it’s a type checking error.
Maybe a better solution is rust style “compiled doc strings” so the documentation can’t be wrong, or rust style inline unit tests that show examples of how to use functions... but python has neither.
It’s been my experience that type hints for your own code... eh. But if you’re working in a team, they’re valuable.
charge_customer(order_details: dict)
That fact that it’s a dict is the least of my worries. I need to know what goes into it, for which I need to either read the docs or the code. So you try to organize it more and avoid using something like a dict of order details. Instead you create an order object that has methods like add_item(id, qty). Can you guess what those are or deduce it quickly by looking at docs or the implementation of the method? Most likely. I’m not saying that type hints won’t help. But as far as just type checking at compile time they only really work as a very obtuse tool, and at runtime they are a very feature-barren form of contracts.You can do it in other ways, sure... but I don’t accept that you can argue type hints have no worth.
... if they are the correct solution for every case? If they are worth the (honestly fairly trivial) effort to write and maintain?
Those are fair questions.
The question is, if you’re or using type hints what are you using instead?
...I how the answer isn’t “nothing”, because that just means you not using a tool because you don’t like it, not because you have a good reason.
I can't personally guess the type of the `id` argument to the hypothetical `add_item(id, qty)` function, but I would guess that the source code for that function would look like
def add_item(self, id, qty):
self._items.add(id, qty)
I would also note that python type hints can be charge_customer(order_details: Dict[Product, int])
which is significantly more helpful than just knowing it's a dict.I also use it a lot at boundaries where the modality changes, eg going from local data structures to RPC, where the data shape is the same, but there is io and fault tolerance to deal with.
For example I'm sure you've had runtime production errors where you tried to call a method on a null object many times. That is the kind of thing last generation type systems couldn't help you with, and new systems do.
It's the one thing that I think really holds back go from being phenomenal (I still love it), and I hope generics help with that.
(In statically typed languages with implicit nullability, nulls create the same problem.)
Basically, explicit contracts are Good, and types are a subset of explicit contracts that are more easily enforceable than most.
Same here. The arguments for it seem to be:
- It allows you to find bugs in your code without running it. I’ve concluded that my workflow must be different from people who make this claim - I never write code without testing it. This is more comprehensive than relying on a type checker, because it finds all kinds of errors, not just type errors.
- It’s a form of documentation of function parameter and return types. This is better served by comments, because type hints are usually inaccurate in a duck-typed language like Python. For example, I see hints like `def twice(x: int) -> int: return x+x`. The correct type for a function like that isn’t `int`, it’s something like `addable`.
If you know that x is only ever an int then you can safely change the implementation to `return 2 * x`. Sometimes constraints give you extra possibilities.
But perhaps that only makes sense for some functions. Probably not twice().
Neither do I. IDE integrated typecheckoing still finds the kinds of bugs typing can find faster than IDE integrated unittesting, and often as they are being typed.
> This is more comprehensive than relying on a type checker, because it finds all kinds of errors, not just type errors.
It's still slower for the very broad class of errors a typechecker can highlight than static analysis, and unless you are very good at writing tests, far less comprehensive in that subcategory.
> It’s a form of documentation of function parameter and return types. This is better served by comments, because type hints are usually inaccurate in a duck-typed language like Python.
Conversely, it's better served by analytically confirmed annotations, because comments are usually inaccurate in most languages.
> For example, I see hints like `def twice(x: int) -> int: return x+x`. The correct type for a function like that isn’t `int`, it’s something like `addable`.
It's `int` if that's the promise I want to make to users that they can rely on and that I will fix bugs against and that I will adhere to in future changes that aren't SemVer major. That the implementation can supporting something broader is a private detail, not part of the API presented. It might use an optimized routine that relies on implementation details of Python ints tomorrow and just be using + today because correctness and delivery schedule beats optimization in this case. In fact, that possibility would be the only reason to a abstract something a simple as application of a simple binary operator
OTOH, I frequently see argument or return value specs in comments that include types that are plain wrong, not subsets of what the underlying code supports.
Guess you're just a better programmer. I've run into many in JavaScript and Python.
About the only thing I can think of is that I still routinely switch to C for Arduino projects so I am cognizant of things needing to be the right type and don’t rely on automatic casting like you get in PHP and JS.
There are many, many things that can be expressed with types... depending on the type system. In Haskell, when you look at a function type, you instantly know whether it returns an optional value (a Maybe), or if it has side effects (IO), etc. You can also tell how general the function is, by how abstract its parameters are.
An example of something that becomes possible with a rich type system: https://hoogle.haskell.org/