Statically typed languages are easier for the reader because you can see the types and quickly jump to their definitions (or even just hover over them in some IDEs).
They're easier for the AI because they provide natural guardrails and feedback to guide it, as well as much more confidence to the programmer that the code does what it is supposed to. Rust even provides strong guarantees about correctness across threads, which is so helpful to multi-threaded code.
The fact that they run faster and use less memory is just icing on the cake.
Even just last year the AI could not handle the borrow checker well. Today I think it is better than me at handling tricky lifetime issues that ocassionally happen in multi-threaded Tokio code. I've been doing almost 100% Rust development over the last 3 years, and the experience is now very good. I don't write code by hand any more, nor do any of the 50 engineers where I work.
I imagine it does quite well with Go, since it's such a simple language. And Go is very readable, and compiles very fast. If you can afford the GC in your problem domain, it might be a good fit. You would have to be so careful with introducing concurrency, because it would be so easy to introduce race conditions that both the AI and human reviewer might miss. I haven't tried to use Go in anger yet with LLMs, so this is all just speculation.
However, verbose typing is likely a negative for LLMs.
Algorithms written in "pseudo-code", aka a higher level language without type information, are far more readable to a human, and thus likely an LLM too.
In regards to control flow and general concept of what code is doing, types provide very little info over well named variables. In fact they often impair understanding by breaking up logic with implementation details.
I'd be curious to see some experiments around this, but I'd guess strongly typed languages where the type information is mostly hidden/inferred would have better generation accuracy from a semantics perspective (and likely worse from a type safety perspective, but can be corrected on compile/retry)
What’s the basis of this claim? There are many many more lines of code LLM’s are trained versus pseudo-code.
Also I agree, anecdotally the self-correction is key benefit from static types. If there is a mistake, it is caught at compile time and not at runtime.
Humans are trained on human language. LLMs are trained on human language.
Thus something that is easier for a human to understand is likely easier for an LLM to understand.
That higher level language with well named variables reads more comprehensibly than code:VERB with:PREPOSITION types:NOUN, intermixed:ADJECTIVE, stems:VERB from:PREPOSITION first:ADJECTIVE principles:NOUN too:ADVERB
Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo
versus
Buffalo:PN buffalo:N Buffalo:PN buffalo:N buffalo:V buffalo:V Buffalo:PN buffalo:N
I think the second one makes much more sense.
Static typing is, roughly, where variables and expressions have fixed types that can be determined ahead of execution. Strong typing means the language doesn't offer implicit type conversions. Python is dynamically typed, i.e. not statically typed, and strongly typed. (Ignoring its type annotations feature, of course.)
Types guarantee invariants at compile time, adding type info to a variable name is just a prayer that the next human or robot will enforce the invariants with respect to that type when it matters. This is like saying you don't need a saw stop because you should just avoid sticking your hand in the saw blade.
Comically, I’ve witnessed people say this since the 90s.
For me, I don’t care about static because dynamic is easier. For the very few conditions where it matters, I’ll use static. Otherwise I like the simplicity of dynamic languages, especially python. IDEs provide support and jump to definitions in dynamic languages, too.
However I've definitely noticed that the larger a ruby program gets, the more likely I am to manually add type checks. Beyond a certain size I simply can't fit everything in my head at once. Even though these checks are still done at run time, debugging is much easier when I can find out ASAP when something is not what I expected it to be.
People often say "that's what tests are for!". But if I'm spending time writing tests that verify the types are correct, I see that as a waste of my time because that's exactly the kind of thing that a compiler could do for me in a statically typed language.
For any long-lived code base, dynamic piles up invisible problems over time.
It's great for short-lived throwaway stuff, but as soon as you know you'll be maintaining a large code base for a long time, the "easier" part of dynamic actually becomes harder than just spelling stuff out.
It's obviously a trade-off and not everyone agrees, but that's my personal experience having run large eng teams for both types.
If it were up to me, I'd start out by using the AI to port a Python web app to a compiled language like Go. Then we could maintain that going forward instead.
In reality, a Python code base is maintained by Python programmers, most of whom would vote against such a change.
I personally find that AI writes better Scala than Python.
With AI, I don't know. If we're forced to use types, the AI does that work for me, but that added verbosity can't be good for it.
To me, this argument sounds similar to “making salads to eat reduces health because they waste time that could’ve been spent on working out” - it assumes the time savings will be spent on working out and not on sitting on the couch.
In the case of SWE, any time saved will always be spent on “2 more features we think we can ship this sprint if we deprioritize these pesky ‘additional tests’ tickets - don’t worry, we’ll circle back to those next sprint of course”
* someone assumed duck typing where it wasn't or the inverse. Or changed the assumed interface of a duck.
* somewhere doesn't handle None properly even though it's a valid agrument.
* making sure every function properly checked that the input parameters were valid and generated a meaningful error message
* making sure side effects of the ducks and the meta-bs didn't break other things
AKA all type related nonsense.
With go, and even more-so rust, the time the compiler saves me by obviating all that type related testing is far larger than the time spend dealing with the type related testing. Even when you factor in the extra time twiddling with types adds to the coding. And don't get me started with the whole "deal with type bullshit in dynamic languages" mess that occurs when a bug slips through into prod....
I know the whole dynamic vs static typing is an age old flame war, but it sure seems that static is winning to me.
I wouldn't be so fast. It wasn't that long ago that the dynamic zealots were declaring victory. And before that the static zealots. And before that the dynamic zealots. Going back decades.
But at least I can't imagine how this trend reverses course now.
That's exactly what those/you dynamic zealots were telling people like me 15 years ago :-)
But yes, I largely agree with you. My $0.02 is that the larger pendulum over the decades is a trend towards static but being less and less visible to the end user, increasing the static typing while reducing the boilerplate and overhead on the part of the developer. Think things like type inference and the sort. Even as a static typing fan I don't miss the days when literally every variable needed an explicit type annotation.
Is anything like that happening?
Reduces manual boilerplate and visual noise while retaining static typing semantics.
My own experience with agents, I'd summarize as "the more the world model (which the LLM does not have) is not concretely represented by the text, the worse LLMs are at it."
So it's _great_ at HTML, CSS, markdown, and most cursory-inspected English. Good at javascript. OK at most languages. Then very bad at concurrent programming and closely-inspected English.
I also don't think your top-line conclusion is right at all. I'm quite the opposite opinion. The types "working out" does not actually give me hardly any conviction that the code actually works. And notably, LLMs seem good at making types work out (they're in the text!) but then still have code that's not actually at all right (for the world model).
I also find that types are not worth the often COPIOUS amounts of boilerplate that comes with them. Some of the worst code I've seen is using reflection to make something happen that would otherwise barely be metaprogramming in Python or Ruby.
But that's not to say types are useless. I just think rigorous static typing is not worth it. My current favorite way to program is Python, with an enthusiastic use of type hints, enforced by a good type checker (pyright). It gets you 99% of the benefits of traditional static typing, but you can also just tell the type checker to just look the other way for a moment if you're going to commit a dynamic typing.
Now imagine the same principle works with backend services, e.g. we've enabled nrepl endpoint in our staging k8s service, we can modify the behavior dynamically, like adding a new route, for that we'd just need to connect to the REPL, write something like `(POST "/v1/new-effing-route" request ...`, eval it and voila. We don't have to re-deploy, recompile, even save that code - it would just work, like magic.
Now imagine giving this ability to an LLM. It won't have to guess, it won't have to go into write/compile/run/restore-the-state/try loop - it knows what's available, what can affect the behavior of the system, etc. It works surprisingly well and saves tons of time and tokens. Kids who have not tried that, have zero idea how great that is.
It can even use REPL access to investigate bugs, cause it can run whatever inputs it wants on whatever functions it wants. It can tweak functions and retry stuff. It’s really ridiculously cool. AI programming with clojure is nuts. It can solve WAY harder problems, including with libraries and domains it’s never seen before, cause it can struggle through em just like a person would!
In the early days (before Claude Code mastered Rust,) I would get into this annoying pattern where Claude used different names for variables between tests and implementation, get confused, and then more times than not, would change the implementation to match the test (which was not written first--was not doing TDD and thus not the behavior I wanted.)
Static languages prevent that. I've had great success with Claude writing Rust, and I think it's an excellent language for LLMs not just for low level work, but for production-grade code of all types (I see rust as better aligned to compete with C++, Java, and C#.)
I've also had great success with Claude writing C#. Using Claude, I've built C#/.Net in Linux, deployed in Windows (via Visual Studio) with Claude Code running in WSL, and it's been a great experience all around.
People have been "calling this out" for decades. Yet the most productive languages are still dynamic/strongly typed.