Hacker News new | ask | show | jobs
by pmontra 6 days ago
Hallelujah, that's always been my position. To the static typing folks: leave my dynamically typed languages alone and go coding with something that really suit your needs. If the answer is that Python, Ruby, JS, whatever are really much more pleasant to code with, my reply is that they are so precisely because we don't have to type type definitions. Tradeoffs.
13 comments

It's not an all or nothing thing.

I think types are particularly valuable for libraries. A library author using copious types really helps the downstream user to know "Ok, this function returns a dict(Foo, Bar)". But after that, it's a matter of preference if you want to add those types to your own code or not.

Having the types in the libraries makes it a lot easier for your tools/IDEs to give good suggestions and catch bugs that you might otherwise miss.

Yes, where would I be without the _RelationshipBackPopulatesArgument type of

        sqlalchemy.orm.relationship(argument: _RelationshipArgumentType[Any] | None = None, secondary: _RelationshipSecondaryArgument | None = None, *, uselist: bool | None = None, collection_class: Type[Collection[Any]] | Callable[[], Collection[Any]] | None = None, primaryjoin: _RelationshipJoinConditionArgument | None = None, secondaryjoin: _RelationshipJoinConditionArgument | None = None, back_populates: _RelationshipBackPopulatesArgument | None = None, order_by: _ORMOrderByArgument = False, backref: ORMBackrefArgument | None = None, overlaps: str | None = None, post_update: bool = False, cascade: str = 'save-update, merge', viewonly: bool = False, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, hash: _NoArg | bool | None = _NoArg.NO_ARG, lazy: _LazyLoadArgumentType = 'select', passive_deletes: Literal['all'] | bool = False, passive_updates: bool = True, active_history: bool = False, enable_typechecks: bool = True, foreign_keys: _ORMColCollectionArgument | None = None, remote_side: _ORMColCollectionArgument | None = None, join_depth: int | None = None, comparator_factory: Type[RelationshipProperty.Comparator[Any]] | None = None, single_parent: bool = False, innerjoin: bool = False, distinct_target_key: bool | None = None, load_on_pending: bool = False, query_class: Type[Query[Any]] | None = None, info: _InfoType | None = None, omit_join: Literal[None, False] = None, sync_backref: bool | None = None, dataclass_metadata: _NoArg | Mapping[Any, Any] | None = _NoArg.NO_ARG, \*kw: Any) → _RelationshipDeclared[Any]*
It's not for you, it's for your IDE. And if you aren't using an IDE then you can pretty much ignore it anyways.

You are in exactly the same position as if you knew or didn't know that type.

If you're not using and IDE nor an LLM
>> I think types are particularly valuable for libraries.

> Yes, where would I be without the _RelationshipBackPopulatesArgument type of ...

(proceeds to list a signature with over 40 parameters)

You would be left wondering which of the 40+ arguments provided to a given invocation is not what was allowed without a compiler to tell you.

Have fun tracking down which one, or ones, is causing the problem.

What function signature isn't going to look messy with 36 keyword arguments.

https://github.com/sqlalchemy/sqlalchemy/blob/0798e6cbe11b30...

Part of it is due to the clunky `_NoArg.NO_ARG` business for optional params. Pretty-printing it would also go a long way, but that technology seems too advanced for any language circa 2026.
This is a big part of the reason that I've embraced ths sqlc (d/re)evolution.

Writing queries in sql and then generating for the target language also provides a flexibility that has reduced rewrite cost. Add to this ease of organization and layoit, and I'm not going back.

It's probably hard to come up with something messier than SqlAlchemy here. Not an expert, but spent more than enough time spelunking queries in the debugger. I much prefer bugs that can be surfaced at compile-time rather than run-time.
_RelationshipBackPopulatesArgument = Union[ str, PropComparator[Any], Callable[[], Union[str, PropComparator[Any]]], ]
I do think it is somewhat of an all or nothing thing. I can write dynamic languages, sure; I prefer having static types, but I have written a lot of dynamically typed code. However if I'm working in an editor with LSP integration, the experience is much worse when some things are missing types.

As an example, I may have a variable with types:

    const something = somelibrary.getSomething();
and I can type `something.` and see methods and properties. However, if my own code doesn't use types consistently, it's so easy to lose type info. For example:

    const something = someWrapper(someLibrary.getSomething())
or:

    function doSomethingWith(something) {
        ...
    }
    doSomethingWith(someLibrary.getSomething()
or any number of other patterns which accidentally strips type info from the variable if you don't use types everywhere.

I would much rather have a language where the compiler complains if some variable doesn't have a static type, than a language where I can accidentally leave something untyped. I don't understand which case I would want a variable or function to not have associated static type information.

> It's not an all or nothing thing.

It kind of is? All the partial-typing systems are too complex and usually broken in various ways. Compare to eg Elm or Gleam which are typed and super simple.

It kind of isn't. We are talking about using types in type optional languages. We aren't talking about the quality of those type systems or whether or not they are good type systems.

If I was comparing type systems then it'd be relevant to talk about statically typed languages like Elm or Gleam.

This is even worse because you attempt to try to sell why types SOMETIMES make sense. But you aim with this for a language that did not have nor need types to begin with. People don't seem to understand that this is an issue.

The library-situation is really not different from having types everywhere, and some people will do that too.

> catch bugs that you might otherwise miss.

People repeat this a lot. In about 22 years of writing ruby code, I have never ran into a situation once where I would have caught a bug through types. I don't understand why people keep on repeating this. Repetition does not make it anymore true.

Think in the opposite way: if types would have been necessary to begin with, why would ruby have been successful back in 2006? It was successful without types already. And types were never needed - they came because some people THINK they are needed. This is the biggest problem - the thinking part. They think they are right and all who do not use types, must be wrong and very foolish people.

Have you considered these people in general aren't some outsiders out to attack you or your favorite language?

The people who do end up making and using type checkers are people who have or are actively using these dynamic languages and found out that they CAN help THEM with preventing bugs.

Also, really? 22 years in which not one type-related error happened? Never? I don't want to say I don't believe you, but I really don't.

> In about 22 years of writing ruby code, I have never ran into a situation once where I would have caught a bug through types

You must be the world's greatest programmer with perfect memory. Every nil pointer exception is a bug a (good) type checker could have caught. You've never had a NameError or NoMethodError in Ruby?

This is perhaps the least believable comment I have seen on HN, ever. It would be more believable for someone using C to say "In about 22 years of writing C code. I have never ran into a memory bug".
This is not as uncommon as you may think.

Avoiding "memory bugs" in C is trivial, but tedious, so too many C programmers fail to use an appropriate programming style. Nonetheless, there are some who have never encountered a "memory bug" in programs written by them.

I agree that a programming language should enforce such features, instead of counting on competent programmers.

SRE here, I've had multiple outages caused by lack of typing in both Ruby and Python where bad types get passed, something doesn't catch it and either data corruption or constant crashes. Couple cost us big money because it screwed up billing and we were forced to eat the billing cost.
>In about 22 years of writing ruby code, I have never ran into a situation once where I would have caught a bug through types.

I've definitely ran into that although much less common at places with good test discipline.

I think the related and often conflated problem is errors caught by compilers which you don't hit til runtime in Ruby/Python without good test coverage. For example, referencing an undefined variable

> In about 22 years of writing ruby code, I have never ran into a situation once where I would have caught a bug through types.

In 22 years you have never seen `nil` show up in places it wasn't expected? Really?

Your app didn't silently break when you upgrade rails or any other gem?

If ruby was statically typed the typechecker would have caught it.

Personally I like having my TypeScript cake and eating it.

I also truly believe those who design type systems would benefit from taking a look what kind of code people programming in dynamically-typed languages produce.

I do too, but I feel like TypeScript stands alone as an unusually effective and pleasant to use bolted-on type system. I've not seen any other approach come close. (My sample size is Python, Ruby and Elixir)
I really like PHP's type hints (I think they were the first I used) though it's somewhat limited (can't type hint complex/nested structures last time I checked).

Flow for Javascript was okay but Typescript I've found to be much nicer (last used flow years ago but occasionally I'd encounter bugs in Flow).

Python's is okay but it feels clunky.

you don't think the elixir type system is effective? I've never seen a bolted-on type system get so much acceptance from the hardcore "you can add types into my dead hands" crowd
I find it funnysad that python people coined the phrase duck typing and then ended up designing what they have now. Meanwhile TS manages to embody duck typing far better even though coming from very different background.
Does Python needs its own TypeScript moment? Many times, while writing Python and deeply frustrated with its weak(er) type system, I have dreamed of something like TypeScript or VB/VBA from the early 2000s (where the type system was surprisingly strict!). However, there are so many Python libraries written in pure C, it is way harder to create a TypeScript equivalent.
Could you point me towards the kind of code people programming in dynamically-typed languages produce?

I have lived in statically typed languages almost all of my life, and even when I don't, I pretend I do, just without having a typechecker. So I'm very curious about what I'm missing.

Any Rails app. A random one: Redmine. You can look at this file and browse the rest of the repository.

https://github.com/redmine/redmine/blob/master/app/controlle...

I hate TS's tooling with a burning, deep passion. But its type system is actually pretty incredible for what it is.

There are times that I yearn for TS's ability to do duck type reasoning in e.g. Rust (despite that not being feasible) when working with very large data types.

The only reason I gave up resisting and started writing any significant code in Python at all was that it got some kind of type system, and thus became less unpleasant to code with.

"Pleasant to code with" does not describe getting "AttributeError: 'NoneType' object has no attribute 'foo'" 25 levels deep in a stack trace already obfuscated by dynamic object-oriented nonsense. In production, because it's an unusual case and testing missed it. Not that test cases aren't way more work than types anyway.

> To the static typing folks: leave my dynamically typed languages alone

Surely you understand that the push to add types to dynamically-typed languages comes from dynamic-typing folks, not from static-typing folks. People who are deeply into static typing have little incentive to consider e.g. Python, whose support for types is relatively weak, loosely-defined, and rarely-enforced compared to the statically-typed languages that exist today.

> People who are deeply into static typing have little incentive

Except when their boss tell them to use Python, or they rely on one of Python libraries that their pet language couldn't provide via its powerful type system.

Doesn't it come from folks that are forced to work with dynamically-typed languages but can't be arsed to understand them?
understand?

It's really easy to understand that everything is typed as Any/Object/whatever upper bound type your statically-typed language of choice uses.

Desiring something better does not mean a lack of understanding of the status quo.

Can you elaborate?
Let me paraphrase the summary of Eloquent Ruby by Russ Olsen:

    It’s easy to write correct Ruby code, but to gain the fluency needed to
    write great Ruby code, you must go beyond syntax and absorb the “Ruby way”
    of thinking and problem solving
My engineers write better code when we enforce types.

It's easier to do this then retrain everyone on Go and rewrite all our code.

New stuff is often in Go now, but prototyping quickly in Python and then enforcing types when we have to get it ready for production has been working decently

Totally agree. I hear a lot of rust makes it hard to write incorrect programs. In my experience it makes it hard to write programs in general.
God, I hope you never touch a production code base with real users. Strong, static typing has won. It is a must for serious software development. The time and money saved in stupid errors that are caught and avoided before the software even runs is enormous. For internal tools and one-off scripts, sure, go nuts with dynamic stuff, but there is no reason to use a dynamic language for code paths that actually make the business money. If you cannot specify precisely the type of every piece of data you touch and all the operations that can be performed on it, you're not doing software engineering, you're spitballing. And if you want to spitball, LLMs are here and they're great. You can type in a very loose description indeed and get properly typed Rust out the other end.
In fairness, Python code with good type annotations is quite maintainable, but well over 90% of Python programmers and managers are unprofessional enough to not care. That's where a statically typed language adds value.

It is important to be able to understand the LLM generated code, often also to edit it by hand at times. Go lang has the middle ground whereby it's understandable with moderate effort while being statically typed with any necessary flexibility.

I am totally with you and I am glad I am not the only one who is totally against those type-addictions leaking into languages that did not need them in the first place.

Types in ruby are even worse than in python, because the type systems in use really make ruby turn very ugly. In python it is not as much as a huge problem with regards to syntax, as python has a stricter syntax (e. g. mandating foo.bar() whereas in ruby you can typically omit the (), among other syntax sugar examples).

We need to keep the type people out of those languages.

Many years ago, on IRC, on #haskell, they said they don't want everyone to use Haskell. Back then I did not understand it. After the type-addicted people emerged out of nowhere, I now begin to understand why Haskell is so snobbish. If you let every idea float, you end up ruining languages - and then those who wanted this, will retire and move away too. Ultimate damage factor caused as outcome here.

> my reply is that they are so precisely because we don't have to type type definitions.

My reply is that no, that's not why they're pleasant. If that were the only criteria, we could conclude Python is only as pleasant as Forth.

So far I have been avoiding Pydantic as a huge-ass dependency. Instead I am relying on standard type annotations, lots of typed dicts and at service/program boundaries use a jsonschema. I like being able to specify the type of most functions, and get some hints, completions and so on, but I don't want to _have to_ specify every darn type. I also don't want to write a class for everything. Typing dicts is good and usually sufficient. If I wanted to write types for everything, then I could also just write Java or Rust or similar.

Unfortunately, I think the kingdom of nouns faction has long invaded the Python world and I see more and more companies demanding Pydantic and similar things. They are dragging us all the way to Java land, it seems.

I tend to get triggered when TypeScript is painted as “JS with type hints”. Coming from Python background, TS and Python with type hints are just so different.

With Python I can’t see myself type-annotating everything (or bringing in pydantic anymore for that matter, it is indeed becoming a blight), but with TypeScript my process is turned on its head: I find it natural and easy to start writing with types and have everything fully typed, and I find the fact that it simply won’t compile if anything is off (compared to Python where it’s more like “one of my N type checkers/linters failed, oh well it still runs though) a useful constraint that gives peace of mind.

I've only used Mypy so maybe I should try some more typecheckers. But I've found Mypy and the annotations it uses don't get in the way much, and they make function signatures usable as documentation, so they're generally a win. But they do let a lot of incorrect code through that would have been caught by typechecking in e.g. Haskell. Thus the idea of using 5 typecheckers (not serious I hope, but I understand the sentiment).

I hate JS and have wanted to try Typescript. Is it really JS with type hints? I had thought it was really a different language that compiles to JS, like a less hardcore answer to Purescript. I did like what I saw of Purescript but it never caught on.

I feel like Python itself lost its way during the Python 3 transition and now seems declining like Ruby. IDK what to use instead for low-boilerplate projects though. So I still use Python.

> Is it really JS with type hints? I had thought it was really a different language that compiles to JS, like a less hardcore answer to Purescript.

No experience with Purescript, but otherwise yes, a language with a different feel. Beats Python in my books. Typings do also serve as documentation but errors won’t compile so it’s not just linting. Make sure to use strict mode so it forces you to pay even more attention to types.

I still occasionally use Python, but TypeScript is my go-to currently.

I started using types with Python in 2018-ish, and I never looked back.

I am not that good a programmer, so maybe I am wrong, but I just like being able to tell what the data is that's moving through the system. Typed function signatures, a little shift+k here and there, a warning that I am trying to add int and a string. I don't see what's the harm in having that?

At the end of the day, if you don't want to use Python with types -- do not. Unless somebody at work is forcing you, and it feels like putting lipstick on a pig (especially with something like numpy that doesn't easily support types)? Then condolences.

But why does your appreciate of type systesm not lead you to something like Typescript? Which is a lot more robust? Or Rust? C#?

I guess my speculation is that not every language is good at everything. Sure you might want a better type system with Rust. But for data science?

In practice, inertia is stopping me.

For personal projects, I don't want to learn Rust just so I can do `def add(a: int, b: int) -> int`.

For work, I don't really get a choice. I work on brownfield projects. We do use TypeScript, thankfully, for all the browser bits. But nobody is going to stop to refactor a 5 year old production code base from Python to Go just for better types. And -- pepega -- definitely not our codebase that's full of data sciency stuff (numpy/opencv/pandas). So we live with a not-as-good-as-it-could-have-been type system.

Compromises, man %) One of the constants in life.

Meh, the amount of effort required to keep up to date with the python ecosystem churn is around the same as learning rust. More so if you are starting from scratch.

I quit python after realizing the amount of effort it required to just implement the tooling for a project… when all of that comes included with rust. I have spent maybe an hour in the last year thinking about tooling. Glorious.

But yeah, I feel for you. It is an impossible sell when they pay off is impossible to understand without a Time Machine and the only thing known about the cost is that it’s high. But for new people and projects, I can’t imagine starting with python in 2026.

Which Python tooling? I know that uv is replacing pip but all of my costumers' projects still use pip. One of them installed python with asdf. I can't think about any other tool we are using except Claude, but I don't think that's the kind of tool we are writing about. We deploy with a custom bash script resembling Ruby's Capistrano. Those projects are web apps with server generated HTML.
What specifically makes them more pleasant? (not a rhetorical question, I want to know what's important to you)
Historically (80s/90s) I started using Perl because I didn't have to write all those malloc and free I spent years writing in C and I could perform string operations much more easily. Then in the mid 90s because of that wonderful CGI.pm Perl module. But the plus of all those languages, and Java, was managed memory. Then in the mid 2000 I learned Rails, and after Rails I learned Ruby. It was like Perl but much easier to understand and again no types to type. Basically what I did in Java but in a fraction of the time and in a fraction of the lines of code. Then a customer asked me to work with Python on a Django app so I learned Python. It looks like a Ruby designed by Klingons but it's OKish.

All those bugs I constantly read about, they don't happen very often and are a good tradeoff. Maybe Rails and by Django are shielding me from some bug scenarios.

I'm not trying to be cute here, but it seems like you have mostly been using scripting languages without static typing. How do you know what it is like to develop in languages with strong typing that produce binaries and that these are unpleasant to use?

And dabbling doesn't really count. It takes time to actually learn a language. Much longer than most people are willing to admit.

> because we don't have to type type definitions

Typing "type definitions" makes you type less, not more, because you type the definition only once, instead of writing many tests wherever values of that type are used.

In a decent programming language, one would frequently avoid the need to declare the type of a variable, whenever the type can be deduced from the value used to initialize the variable.

Having types in a language has 2 purposes, one is to enable the compiler to check at compile time or at run type that all the subsequent uses of an identifier after its first occurrence are consistent.

I cannot imagine which are the benefits for the programmers who are against this rule, i.e. who want to reuse the same identifier for multiple purposes in the same scope (N.B. reusing an identifier in the same scope has nothing to do with data types that are disjoint unions or virtual types, which can be used in any type-enforcing language, or with reusing the same identifier in different scopes).

The second purpose of data types is that when the type of a variable or parameter is known at compile-time, that allows more efficient implementations, which are especially important for aggregated data, e.g. arrays.

Again, I also do not understand why anyone would want to have inefficient data representations, to avoid the need of data types.

There has been some argument that when a language does not use data types you might avoid having to rewrite some library functions if you want to change the parameter types at invocation. However this is a problem that has been better solved for more than a half of century, by providing various means to write generic functions that can be specialized at compile-time or by using disjoint union types or virtual types, for using the same function for many different data types, while still ensuring that other data types, whose use would be erroneous, are rejected.

A language without data types saves writing effort only when the programmer omits the run-type checks for correct values, which would be needed to avoid bugs when such checks are not done automatically by the compiler.

I agree that several very popular programming languages with type checking, including C and C++, are very poor examples about how a type system should be implemented, because they require the writing of a great amount of superfluous boilerplate that is completely unnecessary (e.g. writing headers with function declarations instead of extracting automatically the interfaces of a package a.k.a. module or writing explicit type names in a lot of places where they can be deduced automatically from the context).

Such languages are strawmen in a discussion about whether a language must enforce type checking or not.