Hacker News new | ask | show | jobs
by scudd 1918 days ago
I can't help but feel like if you want static typing, you're better off recognizing that python is not the right tool for the job.

Admittedly I've never done serious work with mypy (or typescript), so I'm approaching the value proposition of dynamic typing at face value rather than experience. However, it seems like the primary benefit of these languages was ease and flexibility, ableit at the cost of structure. Or said differently, adding mypy feels like trying to get out of a trade-off decision.

This situation reminds me of a talk Bryan Cantrill gave on platform core values, and as examples he gave his interpretation of the platform core values of languages like C, Awk, and Scala: https://youtu.be/2wZ1pCpJUIM?t=349

For me, platform core values that stick out for python would be Approachability, Simplicity, and Velocity. I understand the posited value mypy brings to the table, but it feels in contention with the original core values that made python appealing to begin with.

8 comments

At this point, another big benefit of Python is its huge number of libraries. This is really attractive to lots of people who also want static type checking.

Oftentimes, the benefits of using Python in a code base that would benefit from static typing outweigh the costs. Especially when tools like MyPy exist, which aren't perfect but help tremendously.

This can't be emphasized enough. I'm presently pushing mypy and type hints hard in a Python project (passing static analysis and type checks is required to get through the CI pipeline) and it's just a compromise. Honestly, I don't want a dynamic duck-typed language at all, but it is what it is. Right now, the single most important factor determining whether we have any future at all is speed to market, not the increased reliability you get from type safety. And Python just has libraries for basically everything. It's enabled us to spin up brand new services that do exactly what we need to do from scratch in a matter of hours sometimes.

With competing priorities like that, this is the best we can do right now. The other factor is the rest of the team is already familiar with Python. They're mostly infrastructure developers. They're more likely to do everything in Bash if given the choice. I'm not going to teach them Scala or Haskell or Rust in the next three weeks before we need to deliver something. But I can at least teach them Python's optional type hints and mypy.

Java also has a huge number of libraries, plus static type checking.
It has many. But I know several companies that move from Java to Python because that's what developers, data engineers and ML/AI experts use.
Projects tend to change over their lifetime. What might have been yesterday's freewheeling experimental prototype is tomorrow's boring mission-critical geriatric legacy. Sometimes this is (or should be) known in advance, but often not.
Sometimes you know in advance, but you still want to optimize for starting and not for maintaining.
Which is why recent language design has converged on sound typing augmented by powerful type inference, so that you can use the same language at any scale. I've worked on ten-line database migration scripts and million-line applications in the same Scala.
As I have in Python. It has issues at the high end, but with tooling they can mostly be overcome. It could be better, but I'm not complaining.

On the other had we have: https://news.ycombinator.com/item?id=26539508

Sounds like not everything in Scalaland is sunshine and roses.

>I can't help but feel like if you want static typing, you're better off recognizing that python is not the right tool for the job.

Static typing is not a job though. It's a language feature. The job is solving technical/business problems.

And Python is a tool, but it's not a tool to do dynamic typing or static typing with, it's a tool to solve problems with.

So, the comment "if you want static typing, you're better off recognizing that python is not the right tool for the job" doesn't quite sit right.

You could instead better say that it's not a good fit for Python.

But why would that be?

Especially since one could easily have said the same for Javacript, but Typescript exists as basically Javascript + types, and people seem to love it.

So there's that.

Typescript a) is just plain a lot better than mypy b) is still a poor experience compared to using a first-class typed language.

The extent to which a) is due to implementation decisions made by typescript/mypy vs being due to inherent differences between Javascript and Python is arguable. Certainly there are things that look like unforced design errors in mypy, and the fact that Dart existed (and largely failed) before Typescript shows that it's not just about what language you're based on. But there are also idioms and aspects of the Python object model that seem inherently hard to type nicely, and are sadly too entrenched in the ecosystem to change.

> Typescript (...) is still a poor experience compared to using a first-class typed language.

I personally don't agree. I have coded in C# and TS extensively, and while first-class types available in runtime (especially with generics <cough>though not in java</cough>) is super nice, I think the benefits of looseness of TS overweight the costs. Also, you can always go crazy and use zod or io-ts, but in that road there's always the danger of just writing types and not doing any work because "typing is fun"(c).

I spent a couple of days trying to make an app work in typescript before thinking "I'll just give Scala.js a quick try, not going to spend a lot of time on it", and to my surprise everything just worked perfectly.
I'm happy to hear that, even though I'd personally refrain from getting myself into a Scala codebase again :) (IMHO, it goes further in the "typing for typing's sake" direction). Because of my existing skills I'd probably use Blazor, if I'm going that way.
Javascript has no real competition. People have accepted its existence as a given so every improvement over it is celebrated. On the other hand, many languages want to be the new python and their fans will try to bring it down at every opportunity.

Note: I'm a python dev, yet even I can agree that the python ecosystem is broken and if not maintained, the language will decline over time.

What's the equivalent for numpy? Tensorflow? Pytorch? The python library support is simply unmatched: regardless of what the base language has as features, the proper choice for many projects is python.
I think that consideration of libraries is a good point, I didn't initially consider, and especially relevant for Python.

I think the one connection I'd make back to my original point is that perhaps python becoming the defacto interface for certain libraries is still a reflection of its core values.

This is especially evident with libraries like Tensorflow, which have interfaces for a breadth of languages, and which the core is implemented in C++. The reason people tend to reach for python to call into Tensorflow is still the core platform values of ease of use, rapid prototyping (imo).

> I can't help but feel like if you want static typing, you're better off recognizing that python is not the right tool for the job.

If the one and only thing you want is static typing, sure.

That's, like, never the case in programming, so it's not really meaningful, though.

In real world programming scenarios, you are always dealing with balancing multiple dimensional problems and “python, with some degree of static typing” is a reasonable component of the solution for lots of them.

> Or said differently, adding mypy feels like trying to get out of a trade-off decision.

No, considering python’s available static type checking (whether mypy, pyright, or whatever, or even a combination) is turning a big coarse trade-off decision into one with finer-grained options, not avoiding it.

> For me, platform core values that stick out for python would be Approachability, Simplicity, and Velocity. I understand the posited value mypy brings to the table, but it feels in contention with the original core values that made python appealing to begin with.

IME using Python’s typing, usually incrementally added, it's not particularly. In fact, it's an enhancer to velocity as the code base evolves.

Python is one of my least favorite languages, but it is a language I absolutely have to use for data work – it would be impractical to rewrite everything in Julia.

Type annotations help a lot. They're not perfect, but for long-running jobs it's a huge help to catch something when PyCharm highlights it instead of 30 minutes into the job.

I'm curious to know what makes it one of your least favourite languages. Could you elaborate?

The reason I'm interested is because I love python and would like to hear differing opinions

Sure! Broadly speaking, I think Python emphasizes a combination of features that lead to code that isn't very composable or decoupled, but without the error-checking benefits you might get in a language like Scala.

For example, consider implementing complex rational numbers. If you have a complex number type, and a rational number type, it should be relatively easy to combine them. Julia does this with literally 11 lines of code in Rational.jl, and 0 lines of code in Complex.jl . In Python, this is extremely difficult, to the point that nobody does it – you would need to restructure the class hierarchy, and you can't do that without access to both.

Another example is the lack of extension methods. For example, if you want to add a bit of functionality to a string or a sqlalchemy engine, you can either add functions (which have a different syntax) or inherit and follow the awful "MyString" pattern.

Python chose to emphasize list comprehensions over map + filter. The problem with that is that you now have syntax that doesn't generalize to other collections, especially custom collections.

Some patterns like async/await, decorators, and with syntax encourage hardcoding decisions when writing a function, as opposed to when using it. This makes your functions less flexible and means you have to write more of them. E.g. consider Julia's do-block syntax[1], which is very similar to Python's with but based on function composition and far more general as a result. Or compare Julia's @spawn to Python's async/await.

[1]: https://docs.julialang.org/en/v1/manual/functions/#Do-Block-...

> For example, consider implementing complex rational numbers. If you have a complex number type, and a rational number type, it should be relatively easy to combine them. Julia does this with literally 11 lines of code in Rational.jl, and 0 lines of code in Complex.jl . In Python, this is extremely difficult, to the point that nobody does it

It's really not difficult; yes, it's more verbose, sure.

> Python chose to emphasize list comprehensions over map + filter. The problem with that is that you now have syntax that doesn't generalize to other collections, especially custom collections.

Python has map/filter/reduce and they work fine. Comprehensions (and genexps) are more concise and cleae for 95% of use cases, I find, so I think the right choice was made there, though I do think people do tend to reach for them sometimes when map, filter, and friends are more appropriate.

> consider Julia’s do-block syntax[1], which is very similar to Python's with

It's more similar to Ruby’s block notation than Python with; it constructs an anonymous function and passes it to another function being called. It is true that with multiline lambdas at all, much less a convenience notation for passing them to other functions, with notation would be superfluous.

> Or compare Julia’s @spawn to Python’s async/await.

Why? They solve different problems. @spawn is equivalent to Python’s concurrent.futures.submit, and it’s buddy fetch() is concurrent.futures.Future.result(). Python has had those longer than it has async/await.

> It's really not difficult; yes, it's more verbose, sure.

Can you point to a case where it's actually been done? I don't even see how you would do it in Python without modifying the standard library or reimplementing significant chunks of cmath.py or fractions.py . In languages like Julia or Haskell this is possible (and relatively easy) as a third party who's just importing the two types.

> Python has map/filter/reduce and they work fine.

Python's map has a few problems, e.g. crippled lambdas make it less useful and it returns a map object instead of the same type as the original collection. It's better than nothing, but it's noticeably weaker than map in other languages.

> Comprehensions (and genexps) are more concise and cleae for 95% of use cases, I find, so I think the right choice was made there, though I do think people do tend to reach for them sometimes when map, filter, and friends are more appropriate.

One big use case where it doesn't apply is numpy arrays or pytorch tensors. It's very common, at least in my domain, for people to initially write code with lists and then switch to arrays for performance. But despite the fact that they should have mostly the same interface, this requires lots of little syntax changes and it's easy to introduce a bug.

Haskell has this issue too to some extent, linked lists are the default data structure and `map` doesn't work with others, you need `fmap`.

> It's more similar to Ruby’s block notation than Python with; it constructs an anonymous function and passes it to another function being called. It is true that with multiline lambdas at all, much less a convenience notation for passing them to other functions, with notation would be superfluous.

Good point.

> Why? They solve different problems. @spawn is equivalent to Pythons submit from concurrent.futures, and it's buddy fetch() is Future.result(). Python has had that longer than async/await.

I do like concurrent.futures, but most blog posts, documentation etc. recommend using asyncio when either one would work, and the majority of libraries at this point use asyncio. I don't think it's really possible to avoid asyncio at this point.

> I don’t even see how you would do it in Python without modifying the standard library or reimplementing significant chunks of cmath.py or fractions.py

If I understand the problem correctly, and you are trying to implement Gaussian Rationals, you’d inherit from numbers.Complex, storing the real and imaginary parts as instances of numbers.Rational (you could use the concrete fractions.Fraction, but I don’t think you actually would need to reference the concrete type to do the implementation.)

There’s a bit of boilerplate isinstance-stuff implementing the operations, which is somewhat tedious, but not difficult (I think its less involved than the custom integral class used as an example in the numbers module documentation, because you should be able to get by only handling same-class, Rational, and Complex cases for most ops.

> One big use case where it doesn’t apply is numpy arrays or pytorch tensors.

Good point. It is not one I’ve hit a lot because of the stuff most of python code use is on doesn’t hit those, but I’ve seen that.

> I do like concurrent.futures, but most blog posts, documentation etc. recommend using asyncio when either one would work, and the majority of libraries at this point use asyncio. I don’t think it’s really possible to avoid asyncio at this point.

When programmers have learned a particular language really well and they encounter a problem the language does not handle well it is usual for them to want to add some capability to the language to handle their problem rather than going off and learning a new language, often because the constraints of time does not let them learn the new language.

There is a tendency to think of pragmatism as throwing away core values in order to solve a problem, although generally this is in the use of the word pragmatism in the domains of business or politics and not in the domain of programming language extensibility.

I can't talk about python, but it's convenient to be able to cast something to any in typescript and get all the flexibility of javascript, but also have the rest of your code type checked if you want it. Statically typed languages usually don't let you mix-and-match static typing with duck typing.