4 ms·
I’ve written a lot of production Python and also a lot of thoroughly and rigorously statically typed code in C++. I use both regularly but there are clear trade
by jandrewrogers 3y ago
I’ve written a lot of production Python and also a lot of thoroughly and rigorously statically typed code in C++. I use both regularly but there are clear tradeoffs in this regard. The main reason to avoid static type checking is that the programming language makes it difficult to do it effectively due to trading it away for other benefits. This does not change the intrinsic value of static type checking.
Python works great for cases where the scope of a particular function is tightly constrained around a tightly constrained set of inputs. In this scenario, it is relatively easy to test fitness for purpose, and that testing can replace static type checking as a practical matter. Many bugs sneak through in Python that C++ would find at compile-time, but that’s okay because the domain is so narrow that you can squeeze them out in testing with minimal overhead.
The minute you start trying to build a more generalized library of functionality in Python, the combinatorial bug factory starts to become a major drag. While you can add type checking, it is clumsy, slow, and less thorough in practice than you would expect with C++. Python is not designed to provide the degree of static type checking something like C++ can. Many types of code benefit from thorough static type checking, and for those use cases something like Python has a high cost.
This is fine. I understand the problems for which both languages are best suited. Right tool for the job and all that. Static type checking is generally beneficial in software but it isn’t always worth paying the tariff if the problem domain is narrow enough. It is quite possible to overfit static type checking to simple cases that don’t justify the effort.
- jongjong 3y agoYes. Great to read this type of nuanced comment. This nails it. Sometimes components are generic enough that type checking does not add any value. Type checking is useful when you want to frame your logic in terms of your business domain. But you don't want your business domain to leak into low level reusable components. Why constrain them to a specific domain when they could suit multiple domains without any effort. Just think of a function which generates a random number for a lottery app. Do you want to call the function 'generateRandomNumber' or 'generateLotteryNumber'? There is no reason to constrain your function to the 'lottery' use case when it could also serve an infinite number of other use cases. The function which is used as a dependency shouldn't concern itself with the specific business case towards which it will be applied. As you move towards the leaves of the dependency hierarchy, the abstraction becomes more about technology and less about business domain.
- a_t48 3y agoI don’t quite follow here - you can still declare interfaces (Protocol) to declare what behavior your code needs.
- jongjong 3y agoIt doesn't work very well for generic concepts. For example, let's say I have a dependency which exposes a function which takes a URLDefinition as an argument. Maybe the dependent logic already has a similar type to represent URLs but maybe it's called URLInfo... The property names might be slightly different. Maybe URLInfo has a Host and Port property starting with capital letters but URLDefinition properties are all lower case. IMO, this is a huge mistake. URLs should simply be treated as plain strings that way you don't need to worry about unnecessary interface mismatches. Inventing complex interfaces instead of just representing concepts as primitives adds unnecessary friction.
- a_t48 3y ago`Or(URLProtocol, str)` Regardless - this is very much a strawman. I could (try to) pick apart the example, but I'm not going to. Sometimes a complex interface is required, sometimes a simple one.
- Thiez 3y agoThe approach you suggest is commonly referred to as 'primitive obsession' and is considered by many (including me) to be an antipattern. Having primitives everywhere means you have to repeat validations everywhere, methods will have more parameters, and it's much easier to accidentally pass these parameters in the wrong order.
- jongjong 3y agoYou can pass simple 'info' objects. So long as they're just raw objects/data and don't expose methods. The main idea is that interactions between components is about messaging/communication. Parent components give their children the necessary info to do their jobs but the implementation details of how they do it is fully encapsulated within the children components. When components start passing other components to each other, they're no longer merely communicating with each other, but they are attempting to micromanage each other's functionality. It's a failure of the separation of concerns principle. There are always alternative approaches; for example, the child can emit/trigger an event to let the parent component know that a low-level condition occurred, but let the parent decide how to handle it because the parent is responsible for that higher level of abstraction. This is not a new concept I invented, it's essentially what Alan Kay (the inventor of OOP) said decades ago. He said that OOP is about messaging. There can be exceptions to the rule, but the idea is to keep those exceptions to a minimum.
- baq 3y agoI’d probably agree with you 5 years ago, but in those 5 years typescript has shown that you can take a completely dynamic language, add a very powerful and well thought out type system and the resulting language is so much better for non-trivial projects. So if you ask me, who has been programming Python since before new-style classes, if types make sense for Python, the answer is a resounding ‘yes’: just steal as much typescript typing as possible.
- LarsDu88 3y agoAnd not only that, but you can take that statically typed code and build JIT compilers on top of them
- BiteCode_dev 3y agoI wish we'd steal more. TS type systel and syntax is much better that what we have. And I hope one day iter[] and callable[] will be made available, cause importing typing sucks.
- Chris_Newton 3y agoAt first glance, JavaScript/TypeScript and Python seem like similar cases, but in practice, I have never found them to be so. The JS/TS community has enthusiastically adopted static typing, to the point where (a) the type system in TypeScript is reasonably clean and intuitive for everyday work, (b) almost every major library uses static types and specifies them in its documentation, (c) almost all major tools and development environments pick them up and work well with them, and probably as a result of those things, (d) almost everyone now uses static types extensively, at least for new development. The Python community seems far more hesitant. I don’t know why. Maybe it’s because we’d just had such a long transition from Python 2 to Python 3 and introducing static typing feels like another big change? Maybe it’s because Python’s static type system simply hasn’t been as clean and tidy as TypeScript’s, particularly before the recent generics changes in 3.12? Whatever the reason, in practice none of (a)–(d) above reliably holds in the Python world. Personally I do still prefer to use static types in new Python code, but it’s frustrating and often needlessly time-consuming when popular libraries don’t.