Hacker News new | ask | show | jobs
by noelwelsh 18 days ago
Wow. Not a Haskell user, but a big user of other languages with expressive type systems (mostly Scala; some Rust). My experience is the complete opposite. I can't imagine using a language without a good type system to catch all the junk the LLM produces. In fact I thought people would move away from languages from poor type systems, like Python, given the cost of using languages with expressive type systems has decreased with LLMs.
21 comments

Recently had to touch a Python project at work. Just setting up the editor needed me to use 2-3 tools out of: pyright, basedpyright, ruff, ty, mypy, and possibly other tools I'm forgetting that kind of do the same thing but throw errors in different parts of the codebase.

Also, for some reason Optional[T] became deprecated, just as the ecosystem finally embraced types ~3 years ago.

In fact, one my company's greenfield projects decided to use TypeScript instead of Python for the [surprisingly] more consistent tooling, and the fact that the big LLM providers all have official TypeScript SDKs anyway. Also, for agentic coding, LLMs don't seem noticeably worse at TypeScript than Python.

My experience can be summarized as:

- for some reason we need 2-3 static analysis tools just for typechecking

- no tool understands each other's comment directives

- each tool reports a different error in your codebase

- even big libraries (e.g. matplotlib) make half their functions return Any

- you'll be tempted to silence the "partially unknown type" warnings, and you'll have to do it for each tool that's running.

> Also, for some reason Optional[T] became deprecated, just as the ecosystem finally embraced types ~3 years ago.

Optional[T] is now T | None. Means exactly the same thing but doesn't require an import. Support for the older syntax presumably won't be removed for a long while regardless.

It is only pyright/basedpyright that flags Optional[T] as deprecated as far as I am aware. Optional isn't actually deprecated by Python or anyone else.

You can disable it in the pyright settings. In my opinion T | None is not a meaningful improvement and insisting on changing it everywhere causes a whole bunch of churn and needlessly makes code stop working on older Python versions.

Optional[T] was always just an abbreviation for Union[T, None], anyway: it's unsurprising that they didn't choose to give it its own syntax.
> for some reason Optional[T] became deprecated, just as the ecosystem finally embraced types

Deprecated in favour of `T | None` exactly because of that embrace. It's cleaner, more consistent (you can `T | U` arbitrarily), and helps slim down the `from typing` imports.

UV, Ruff and Pyrefy and you're set. As someone who works with Python, Typescript and C/Zig quite a lot I don't disagree with you on Typescript, but I'm not sure why you'd pick Typescript over Python. Bun is kind of awesome, but it's also kind of unfinished, but if you go with the default Node I find that the setup for security compliance is next to impossible where Python can do most things with it's standard library, a pandas and pyarrow.

I personally prefer the fake typing in Python because it fits well with our defensive programming style with very low abstraction and little to no adherence to DRY. Since Python naturally force you to deal with runetime assertions rather than getting you to do compiletime checks that then don't actually offer any form of safety at runtime. Which is obviously not a very technical argument, but it just feels a lot cleaner rather than having to juggle the two.

uv still can't build all wheels and afaik they don't intend to do so. Furthermore they leave their users with 0 indication that the build fails because the wheel is unsupported by uv. If I were a beginnner or intermediate, I'd definitely given up after some attempts of fixing the buildsystem/code of the wheel.

I don't get how uv regularly gets recommended without any note about this.

(I work on uv.)

Can you say more? uv should always tell you if a wheel build fails, unless the build backend (which uv doesn’t control, unless you use uv’s own backend) decides to silently ignore a wheel build. This would be a bug in any given build backend IMO.

This is an unfortunate complexity in Python packaging: something like `uv build` can dispatch a wheel build for you, but the actual code that gets run as part of that build is often third-party build backend code that uv itself has little to no control over.

It happened almost 6 months ago, trying to build https://github.com/remsky/Kokoro-FastAPI with uv. From what I can still infer from the dependency list & my shell history, the problematic dep had been pyopenjtalk. After building & installing this dep with pip, I was able to continue to use uv.
We get UV to generate a requirements.txt and then use the Python and Pip which is available on the official Microsoft container images we use for Azure container apps once it hits production. I've never had any issues with the build system in development though.
Can you name some example and explain what happens, that the user is left without indication of failures? I am asking, because I have not had such issues yet, but maybe I just don't know I had them?
In my (really large) typescript project we have like 6 static analysis tools running[1]. Steps to get the project running: install nodejs, install package manager, install dependencies, run project.

The main difference is that in the JS ecosystem it is all installed at the project level, you don't need anything globablly installed besides the runtime and package manager (and even the package manager can be auto-installed as well if you set it up that way).

[1]: eslint, biome, prettier, scass linter, graphql-codegen, tsc, tanstack-router codegen. That I remember, might be more (although codegen might not be considered static analysis, it is needed for static analysis).

You always work in a virtual environment per project in Python, all dependencies are installed in the venv.

So same as JS then.

Why do you need both Biome and Eslint/Prettier?
I'm not who you replied to, but we use Biome for 99% of the linting/formatting, and then an eslint plugin for a few specific i18n scenarios that Biome doesn't cover.
Yeah we had some corner case that still required eslint, although I don't remember what exactly. We also have prettier for a few file types not supported in biome.
Although I also install such tools (linter, import sorter, type checker) locally in my virtualenvs in Python projects. It is possible to do so, but maybe not as straight forward. I have to give the JS ecosystem that much: With the project-local approach, they have done one thing right.
I had a similar experience as you. We normally use Kotlin for everything, but last year we had to do a small project in Python. Setting up the tooling and choosing the tools is quite overwhelming, and the inconsistency between what the tools actually consider type errors is incredibly frustrating. I am actually happy that the project failed and that we don't have to work in that environment anymore.

I think Python is probably good for many things, including scripts and as a starter language, but I don't understand how anyone can stand writing large software systems in it.

Not my experience at all. There are a couple of linters to choose from, and uv is becoming the dominant packaging environment. In my experience it is much easier to write large maintainable projects in Python than in TypeScript. Python has more language features and certainly a much better ecosystem. Who knows where things will go. In the future language choice will be much less important. Languages are largely a human artifact.
You still need a language/platform that performs well. Python and gang do not when compared to things like the JVM and .NET.
(Note: I work on Python tooling.)

> for some reason we need 2-3 static analysis tools just for typechecking

I don’t follow: you need one type checker, of which you have several options. It’s arguably not ideal to have more than one option, but you should never need to run more than one.

- no tool understands each other's comment directives

In general, all type checkers in Python support the `type: ignore` directive, since it’s standardized.

> each tool reports a different error in your codebase

This is a real problem, but I think you can avoid it (like most people do) by not mixing different tools that do the same thing together.

To my understanding, you’d have the same problem if you combined (e.g.) biome and eslint in a JavaScript codebase.

If things were that simple...

I have to use more than one Python type checker because there is not a single one that works. Not only different tools catch different issues. They also have different bugs, and different configuration requirements. Different teams have different preferences.

It's a nightmare. If Python taught me something about typing is that a language that doesn't have a clear definition of types in the reference implementation, it will never get it fixed with external tooling.

Python 4 will have types built in.
> It’s arguably not ideal to have more than one option

It's a complete ergonomic travesty that Python doesn't have one.

I grew up on python and after working with Java I really came to appreciate types. However I do want to point out that big libraries like mpl pre-date most efforts for typing, so it is no wonder that they arent typed properly. A lot of these libraries are trying to improve this but it will just take some time.
Having been around for quite some time, and having used several dynamic languages, my pet peeve isn't types.

Rather I prefer not to be in the same spot I was in 1999 - 2001, with Tcl, and every now and then rewriting code into C, for the application to actually deliver within the performance deadlines.

Python is the only mainstream dynamic language where runtime support for dynamic compilation is such an hassle, where the alternatives do exist, yet are mostly ignored.

Yes, changing a type checker tool is not done easily in larger projects, or on the fly, as they all have different edge cases, where they don't infer well enough, or are more lenient than another tool.

I have switched type checker recently in my own Python projects from zuban to ty. ty seems to work better, and is not a one man show/bus factor of 1, though I respect the work that has gone into zuban by its creator. But ty doesn't understand mypy configuration in pyproject.toml ...

I imagine switching a type checker in a bigger project and with more people involved to be a bit of a PITA, until everyone has adjusted their development environment/tooling. Best one can do is research beforehand which tool suits one best, test it, and then stick to it, unless it has unbearable failures.

I think the author largely agrees with you re: type systems and LLMs. He's pretty explicit that Haskell should be very well positioned to be a power language for LLM-assisted programming, but that the Haskell ecosystem presents the bottlenecks that make it harder.

I don't personally use Haskell for anything, but I use Lean and occasionally some other languages with expressive type systems, and like you I've found it to be a pretty great experience for working with LLMs. But I've also experienced what the author is talking about, with languages that sit at different points on the type system spectrum, regarding a languages ecosystem/infra layer becoming a bottleneck. I don't think it's ultimately about the type system but the broader ergonomics of the language/ecosystem.

So I think his criticism is less than expressive type systems are a pre-LLM concept, and more that Haskell has an individually bad "agentic coding story".

> "At Scarf, we started doing all new API work in Python."

Start the countdown timer for how long it takes them to discover that was a mistake.

Nothing to do with Haskell, but good grief, LLMs do not in any way, shape or form save you from the deep, unfixable problems with Python.

At the very least you need all the static checking machinery like Ruff, Pyright, and hefty unit tests that take the place of typechecking if you don't want obvious failures to only show up in production.

I had this recently with an ML training pipeline, where Python is essentially forced on us. A dynamic error occurred after 17 hours of training - something that a real type system could have easily caught.

The solution that the LLM came up to prevent this in future was a complicated Enum-based system that just made me wish I could use a real programming language.

It is a win win situation, they get to write a new blog post about doing a Python to Rust rewrite.
I know you're sarcastic but still: that is a win-win indeed.
Lol
What would be your goto for the ML training pipeline?

I have the impression that Python basically wins by default in those spaces due to the lack of many good libraries in other languages (except for, like, C++).

But curious if this is just a very outdated view of the world

Yeah Python seems like a bad choice. LLMs seem to write low quality Python compared to Rust, presumably because there is a lot more low quality Python in their training sets than there is for Rust.
Unfixable errors? Why unfixable? Python is Turing complete. I can see difficult to fix, but not unfixable. LLMs lower the bar to refactoring code mistakes.
So is excel.

I actually enjoy python and astral tools a lot, but I think you are grasping for air here.

The only complaint against Haskell was about long compilation times.

I agree that short compilation times are very desirable, but I do not see why Python must be the solution for that.

I do not know whether Haskell can be compiled quickly, but from my experience, I am very certain that short compilation times are easily achievable for languages with good static type checking, especially with compilers that have different options that allow choosing between fast compilation and heavily optimized compilation.

An optimized compilation may require a much longer time than a fast compilation, but that has no relationship with the programming language used in the source text, but only with the intermediate representation used by the compiler and the target CPU ISA. Usually, if you compare the compilation times of multiple programming languages, all the compilation times with fast compilation options are much shorter than all the times with high-optimization options, so the programming language choice may be less important than the chosen compiler and its command-line options.

When you try to optimize a project by generating many variants with a LLM, I doubt that all those variants will be generated from scratch, completely independently, even if only for the reason that when using a commercial LLM the cost of a completely new variant will be much higher, by requiring many more tokens, so whenever possible it is preferable to generate other variants by just patching previous variants.

Whenever a variant is generated by editing a previous variant, incremental compilation can be used, which should be pretty much instant on modern computers.

> The only complaint against Haskell was about long compilation times.

There is also this

> How do we make library docs full of copy-pastable, realistic examples, not just beautiful types?

Which is useful for humans as well as agents.

Haskell indeed has a very bad track record of documenting its libraries. For many people, just having the function signatures is documentation enough.

Rust is equally bad at compile times (if not worse) but its standards for documentation is at another level

> For many people, just having the function signatures is documentation enough.

You make a fair point about a documentation gap. The first step is always defining the problem. Wanting to add lots of realistic examples sounds like a wish for more tutorial or beginner-friendly content. Do you see the problem differently?

On the other hand, a given pure function type only has only so many possible implementations — why tools such as djinn and MagicHaskeller exist or why Hoogle is actually useful, unlike the horror of searching for every `void (*)(const char *)` in C.

https://hackage.haskell.org/package/djinn

https://hackage.haskell.org/package/MagicHaskeller

https://hoogle.haskell.org/

From that perspective, Haskell docs tend to be more expert-friendly — perhaps a rationalization, granted — which seems ideally suited for an in-IDE model to help bridge between developer intent and typechecked code. However, this comes at the expense of putting in the reps to rewire the developer’s brain to think in functional terms and the resulting mind opening and horizon expansion to think new thoughts she wasn’t capable of even considering. In these days of LLMs, fretting over that particular opportunity cost may be thinking nostalgically about the loss of craftsmanship in fine, well-balanced buggy whips.

In the limit now, will all programming be strictly literate?

> Rust is equally bad at compile times

OCaml enters the room...

Is OCaml compilation slow? I was under the impression that it's quite snappy.
Sounds a bit like surrender here.

There's no reason to read the code anymore.

The LLMs produce it inhumanly fast.

They can usually debug and fix problems faster than Haskell can compile the project.

Meanwhile the open source communities are in basic denial about AI, so trying to change things by making compilation faster or advancing a Haskell interpreter are going to meet with fierce resistance.

Whatever. Give up. Just ship Python.

I sympathize.

exactly, i find the article a wierd take. i would have thougt that being able to catch errors at compile time is the assurance that the LLM generated code is actually decent.

so does this mean that the LLM writes code that is so good that the compiler does not find any more errors?

or is it due to the nature of haskell that makes it hard to write bad code to begin with?

or just that because the haskell compiler catches more errors there is less broken haskell code for the AI to train on?

and what does that mean for the switch to python? if the python compiler/interpreter doesn't catch as many errors do we even know that the code is good?

or is this more like the belief if the LLM can generate good haskell code, surely it can also generate good python?

what's the solution here? speeding up the haskell compiler? if that were easy, would it not already have happened?

personally i still don't trust LLM code generation. i didn't learn haskell yet, but what i hear about it makes me more likely to trust that LLMs can generate good haskell code than python.

i believe the future in LLM code generation is code that can be proven to be correct. proving code correct has been a research topic at some point.

"proving code correct has been a research topic at some point."

It has been an area of active research for 40 years. But almost all the research returned the null result, meaning that the program proving didn't improve code quality (basically it didn't work). Yet somehow a group of programmers, usually fresh out of academia falls for program proving each generation. Strong types do really help but you need a good compiler which is sometimes lacking in the real work cough Scala cough. The problem with strong types and program proving is that the juice just isn't worth the squeeze meaning the extra time taken doesn't result in reduced debugging time or improved code quality. I don't think that changes with LLMs. It just exposes the flaws more quickly.

That's literally the opposite of reality; the research and adoption have consistently produced code of extremely high quality, at great cost.

The problem has always been:

- It's extremely labour-intensive, and even small changes to the code can require an enormous amount of new proof work.

- The skills required to formally verify software are very different from the skills required to write it, and the set of people capable of doing so is much smaller.

- Code has to be written with verification in mind, against a specification that expresses invariants that can actually be verified, and that is coherent enough that you can derive useful high-level properties of the system from it.

You needed two teams — verification and development. Verification would always be behind the development team, while also having to feed requirements and design changes back to the developers. Everything slowed to a crawl.

AI changes this. A coherent, verifiable, useful specification isn't easy to write — but it's far easier to write than the software itself, and AI can do most of that work: both drafting the spec and proving that it's consistent and that the high-level properties you actually care about follow from it.

More importantly, a high-level spec is far easier to read and reason about than the reams of code required to actually implement something. Which means:

- AI does the grunt work of writing and proving the spec; humans only have to carefully review that high-level artifact.

- AI writes code it must also prove conforms to the spec, so humans can be assured it's correct without babysitting the AI.

- Changes are driven top-down: evolve the spec first, then have the AI fix the implementation and re-prove conformance.

Our (very, very large) company is rapidly going all-in on formal verification across projects we never would have dreamed of verifying before; the velocity hit and the man-hour cost were only worth paying for truly critical infrastructure.

I’m sure in certain domains this makes sense. However, English is a poor language for doing reasoning in. More and more I’m relying on the code itself as the documentation. One of the superpowers of LLMs is reading code. and turning it into readable English. I don’t keep the English prose around. I delete it. In your example, I don’t see having two sets of artifacts. I see working with an LLM to generate a code base, which is the specification. You still need to have sets of requirements that list the invariants and other parameters. But the process becomes generating the code, and then having the LLM read the code to see if it meets the requirements and invariants.
The issue with formal proofs is that we might end up with correct code that does the wrong thing. I mean, something that the market doesn't care about. At the same time a buggy set of PHP scripts does what people care about and captures the market. Think about 20+ years ago and you find a lot of examples.
Well, sure — and the issue with tests is we might end up with well-tested code that does the wrong thing.
https://xkcd.com/224/ lol

Zuckerberg became rich on php, which grinds my gears almost as much as the genocide thing.

In fairness, my peers at $corp used to ship while I was sad-Wojciech "no you can't create tech debt". The universe is cruel to people who care.

If that grinds your gears, then also take a look at Pieter Levels
php is a great language. there is a reason why the majority of the web runs on it.
the reason is that php was accessible. it provided the bridge between pre coded serverside includes and writing your whole website in code, which you would have had to do with any other language. nowadays many things that php made easy can also be done with client side javascript.

i remember a conversation with a php core developer who conceded that one of the problems in the php community is a large number of inexperienced developers. it is similar problem that javascript faces today, but that, thanks to AI is going to affect more and more languages.

php itself is not a bad language, i use it too if it is appropriate, but it also has improved over the decades. so nowadays i am more likely to use php than i would have been a few decades ago.

There's a reason why littering is the most common crime - it doesn't require intelligence or planning, and whatever you are holding can instantly become trash.
Friend of mine said "Infuriatingly many people got rich with horribly bad software." (Sounds better in German)
I've been successfully practicing programming with proof of correctness since the 1970s. The confusion I see with most people failing at this is that they're trying to write code first and then prove it correct. The idea promoted by Dijkstra, Hoare, et al is to write the code and the proof together, with each of these aspects of the task supporting the other. The result is (in my experience) a better program developed as quickly (even when not counting on vastly decreased debugging time) as pure coding. Similarly with strong lexical typing: One does not try to create a typing model for a hunk of arbitrary code. Instead, one develops the type model along with the code, with each of those aspects supporting the other. Putting it all together: Coding, Typing (and other Constraining) and Proving are done together, with each of these activities supporting the others. It does take time to learn such a methodology. And it takes extra understanding to maintain types, constraints and proofs as the program evolves, i.e. "maintenance".
Can you show us a proof of correctness you've written?
what i expect to change with LLMs is the benefit you get from automated testing. which is really what LLMs need. tools that tell you something is wrong may not speed up a human developer, but they will allow LLMs to make corrections by itself until the warnings go away. so while it may not be worth it to a human developer, it may well be worth it for an LLM.
> what's the solution here? speeding up the haskell compiler? if that were easy, would it not already have happened?

I suspect you’ve nailed the answer: it’s probably not easy, although it’s also possible that it just hasn’t ever had a lot of attention paid to it because it’s been generally fast enough for their user base?

Part of the issue is probably that Haskell build performance is perfectly fine for local development, even on rather large systems.

But in commercial production environments, CI pipelines tend to want to build everything from scratch every time, and that slows everything down. Rust has the same issue. Both languages, by default, compile all their dependencies from source, rather than obtaining precompiled artifacts from a repo the way some languages (like Java) do. And their compilers are slower than e.g. Go's. As the article mentions, various kinds of caching can help with that, but that's extra stuff you have to manage and deal with.

I'm not sure this is a bad thing, though. Haskell co-creator Simon Peyton-Jones coined the unofficial Haskell motto, "avoid success at all costs". I tend to agree with that. It would be difficult for Haskell to maintain its conceptual edge if it were a mainstream commercial language.

The thing I hate about rust is that compiling a small app immediately creates 100gb of junk, and that junk doesn't live in the responsible project's folder, and that junk doesn't get cleaned up by anything.
You're exaggerating quite a lot. Biggest I've seen the cargo directory after 3 months of active Rust development was ~17GB.

You can also limit it with an env var. I have capped mine at 10GB.

I’m at 227GB as I write this.
> I can't imagine using a language without a good type system

have you tried?

i use elixir and carefully watch the agents and its very seldom making typing mistakes (elixir is in-between, it's typed but only as a checker).

ultimately typing doesn't help as much because it's nonlocal information. if the system can locally infer what the shape of functions is, it's way better.

"i use elixir and carefully watch"

- I do not watch agents, at all. Rust and Typescript. When I use Typescript only I have some guidelines so that we build the stack to be as strict and type driven as possible.

Similar for me, I don’t want to babysit agents . I’m sticking with Python for the libraries (data science ecosystem), but I find that imposing ty and a few reasonable ruff rules (imposed with pre-commit so the agent can fix things automatically) improves agent-generated code a lot.
architecture matters more. at least for now, you should watch (not babysit) your agent, because it will slop up your architecture while you're not looking.
I'm watching because I'm particular about architecture. current project (company runs on it, its jira-lite + obsidian + benchling + a lab notebook, mendeley + a lims + a hardware kiosk that uploads scientific measurements directly to the notebook, everything running on cqrs and quill delta for operational transforms) is like ~100k loc and most new features are trivial (i just added a pcr wizard) because claude knows what particular patterns i use (and they are deliberately chosen to be more straightforward that conventional elixir/phoenix), but i do catch claude trying to do something chaotic from time to time. i suspect sometimes anthropic is routing me to a dumber model, because i can sense it happening, and if i keep going, it will make things really bad from an organizational standpoint, abd i have to roll back, but if i go to bed and pick it up next morning its fine.

next features are integrating a small transformer i built to codon optimize and probably "slack" to better communicate with the 2 ppl who work for me.

is your rust+typescript project at that level of complexity?

I'm pretty sure that's the general trend and it will continue.

But I do think what benefits LLMs is the speed and accuracy of feedback. Type systems cover the accuracy part, but haskell was killing them on speed. It seems like a strange choice to go so far the other way on accuracy when there's a lot of languages in between. But I'm not familiar with the project so not in a position to call it.

It's not also really about expressiveness IMO. I've found LLMs to be best with more constrained type systems: they are better at ocaml than they are at typescript.

Java can also have 15+ minute cold compiles on large projects if you kill all caches. It's less bad on smaller codebases because you don't have to recompile dependencies if you target a bytecode vm, but if you always gate feedback on a cold compile in a fresh VM you just aren't gonna beat an interpreted language

But I'd look at people a bit oddly if they said: 'We didn't want to set up CI caching and compiled languages took 30 minutes per run so we changed our entire codebase to python'.

Maybe it makes sense for them, and caching across dynamically spawned VM's is admittedly a harder problem which most build systems aren't great at, but still. I can easily believe that getting build caching to be reliable would be a lot of work, but is it more work than a full rewrite of a significant codebase?

Additionally in modern Java there are even the options of AOT and JIT caches, which can be reused across runs.

Or if staying on Linux, JVM snapshots.

javac doesn't really do a whole lot. Consequently, whatever compile time you are complaining about would be worse with any other compiled language. Most optimization work in Java happens at runtime.
First of all I am not complaining about anything.

Secondly, there are several ways how Java source code becomes machine code, depending on which JVM and JDK is being used, not taking into account the ART cousin.

> I've found LLMs to be best with more constrained type systems: they are better at ocaml than they are at typescript.

When the potential set of behaviors you could write a program to have is infinite, but the actual behavior you want is singular, a programming language is more importantly defined by which ones it eliminates up front than which ones it lets you write (assuming it lets you write the one you want at all, but that's almost always going to be the case for most general purpose languages). Bugs are just false positives in this framing, where the program you wrote seems like the one you wanted, but there's some divergence between what you thought you were getting and what you actually got, and catching some of those up front is a huge part of why type systems are so useful.

It is a bit surprising, I'd have guessed the same. Although in hindsight I could believe that type systems aren't particularly strong as an anti-bug layer. They help. They're a big boon for coordinating large numbers of mid- and low- skill programmers though because it forces them to go further in documenting their function signatures and makes it much more obvious where the problems are when refactoring spaghetti code because things break loudly.

Refactoring spaghetti has become easier in the LLM era because it can just read all the code, and there is now a skill floor on the programmers that kicks in somewhere relatively high. The benefits of type systems might have suffered because of that.

> I could believe that type systems aren't particularly strong as an anti-bug layer.

They're absolutely huge for this, but you have to write code to take advantage of the guarantees that the type system can offer.

As Yaron Minsky at Jane Street put it, "make illegal states unrepresentable". Stronger type systems make it possible to make more states unrepresentable. You end up with what amounts to static debugging - you debug your code at compile time.

Sure, it's still possible for runtime bugs to occur, but entire classes of bugs are eliminated, plus it becomes possible to have static assurances about program states about things that most language don't even try to express in the type system, like security.

Languages like C and Go are so weak in the type system that it barely feels better than fully dynamic languages.
At least in C and Go, you can get structs where it's easy to reason about the fields.

In Python, a "class" is simply a dict under the covers and (by default at least) you can add attributes to it after definition (as well as things like properties). So it's difficult to reason about what the fields are at any given time. And that's assuming people USE classes! I've seen code where all the state is in one giant ever-changing dictionary and you have to pull out a debugger just to figure out what's IN the thing! God help you if you mispell a key!

Maybe you work with better quality code than I do, but I find Go's type system a lot easier to reason about than Python's.

I don’t believe that “reasoning” is very useful in large code bases written by multiple developers. If you are trying to be axiomatic and prove to yourself that the code is correct, there are a thousand different ways the part of the code may not support your axioms in ways that are not apparent. It’s better to have a handful of invariants that are well communicated to the team and a lot of tests.
Yeah this is what I meant, structs are obviously a huge step up in type-safety but the ways you can abuse or bypass the typesystem in C and Go means you still have to be super careful about the memory your code hold, receive or returns.

I think Go developers don't think this is much of a problem because most of them are working on microservices where data is not kept around for a long time (usually a single request). And of course Go makes it a lot easier to handle data-access parallelism. So the benefits of a powerful type system are somewhat diminished in this scenario.

I think it is one of the reasons why Go devs don't see much benefit in Rust, the borrow checker is just not that useful in the context of isolated requests.

The best thing React ever did to the JS ecosystem was hammer in on objects/maps/dicts being immutable. In modern JS it highly unnusual to see code modifying objects (except right after creating them) even though the language itself has no convenient means of representing that.
I agree, I was referring to powerful type systems.
IME Python has been very pleasant to use with types, even though they are not nearly as expressive as Haskell. I've noticed a shift in my own work where I spend more time playing with/manipulating change than I do making sure things type check. That does happen, of course, but it happens with less frequency then when I was writing Haskell by hand. During that time, I'd have stack running tests on file change and it was pretty smooth as well, but that workflow breaks down a bit with the current generation of agent harnesses we have.
It's about the feedback loop being so slow. Agents often compile and run tests to verify their work
The fun part is that the argument holds just as true for humans that write code - we also run tests to verify our work!

Which is basically what people liking dynamic languages have said all the time - types is only good as long as the overhead they bring doesn't cost more.

Right, they run tests too. A compiler is like a quick test before tests. How are you going to cut out that check and let the LLM "write it faster" is beyond me. The compiler catches errors across codebases that today's LLM can't economically or reliably put into context to perform similar checks. They're totally different tools, today.

Also, you can just compile less frequently.

But hey, if LLMs are what drove this person from Haskell to Lisp then all the power to them!

> But hey, if LLMs are what drove this person from Haskell to Lisp then all the power to them!

I didn't see Lisp mentioned in the article. They moved to Python. Which is certainly a choice.

Yes I was being hyperbolic.
Yeah, basically all the Lisp without the machine code generation machinery.
Having worked professionally with Common Lisp, I can tell you that’s not even remotely true.

Besides, “machine code generation” is not a fundamental part of Lisp. Some of the most famous implementations were pure interpreters.

Such as? Having known Lisp since 1996, and avid digital archaeologist, I wonder which famous implementations are those.
Could you go in depth on what you mean by Python being like Lisp? This is no the first time I've heard this but I don't understand it.

Is it just because it's easy to substitute code in on the fly? Doesn't that describe any interpreted language?

> I can't imagine using a language without a good type system to catch all the junk the LLM produces

One approach would be to not use LLMs.

Another approach is career suicide. Both moves are isomorphic in many companies today and will be pretty much all companies in the future.
I don't really understand your comment. Are you saying it's "career suicide" to not use AI slop?
Yes. To add more nuance to what I'm saying the trajectory of the industry and society is heading towards this.

Your company may not be like this now, but the future is where this is all converging. I can tell you at my current company as of now, if you're not using AI it is essentially career suicide for that specific company.

I think these statements are true:

1. An expert using AI is better than an expert without using AI.

2. A mediocre programmer using AI can roughly match an expert that isn't using AI and potentially surpass them in many cases.

3. The companies that aren't using AI are companies that haven't realized that the above 2 things are true.

AI makes people produce consistently worse results.

I will not under any circumstances use AI, not least because it cannot possibly solve a problem I have.

At work, I've already had to "fix" some incredibly faulty projects caused by well-intentioned people thinking they can use AI to write code. I started with `rm -rf *` and moved on from there.

That's you and your anecdotal experience against raw data from the entire world.

I don't get how people like you give their one off experiences and think that validates their world view when the world is changing right before your fucking eyes. To each their own and agree to disagree because honestly your anecdotal experience is right in front of your own eyes too. I just don't get how you can't reconcile that with the rest of reality of basically EVERYONE using AI all around you. How about some nuance like, sometimes AI screws up, but often it does better? That realistic viewpoint may not fit the world view you wish existed and so you choose the most one sided lack of naunce world view you can possibly make.

Do whatever you want. But somewhere in the back of your mind, you know you're just being stubborn.

Wrong assumption. Not all AI usage leads to slop.
The problem in Scarf's case wasn't Haskell's type system, but the long compile time for even small changes.
TFA:

The type safety we gave up hasn’t been noticeable in any concrete way yet, especially considering our test coverage has never been better.

I have heard "the poor type safety" argument from writers of strongly typed languages for many many years. Having written js and python for a large amount of my carrier I can count on one hand the number of times I've found a bug that was due to a type issue. With LLMs it has been the same pattern. They don't seem to produce issues with types.
> I can count on one hand the number of times I've found a bug that was due to a type issue.

Most bugs aren't type issues until you make them be type issues by expressing some business invariant in types.

Refactoring makes an exception not being caught the same way as before ? Type issue. Mixing up some ids ? Type issue. Etc.

Now that can also be emulated with extensive tests. But isn't that a concern for OP as well ?

well i’ve come across it loads, especially 1/2) during REPL style build outs and 2/2) calling libraries and frameworks you are not yet familiar with.

perl another offender… is it a hash? is it an arrayref? over time you get it right, but by trial and error and looping. json suffers this too, arrays different from strings, different from numbers etc, but opaque until checked and liable to change

There's absolutely no way that's true.
My experience is only anecdotal but I can assure you it is true!
It's easy to talk at cross purposes in discussions like this. There are two implicit claims

* Few bugs occurred due to type issues (which I think you are asserting)

* You can design your program with types so that what would be a bug due to a type issue doesn't compile (which IshKebab may be thinking)

Both of these can be true at once

I've never done anything "serious" with haskell, just small personal projects. Mostly this is because I've found the ecosystem to be a pain - when I was trying stack stack was the thing to use but from what I can tell ghcup+cabal now work better.

If you push through that you end up with code written in a language people have used for formal proof (seL4 model is Haskell) and deployment wise a binary that +/- libraries you depend on ought to be reasonably portable.

I'm very surprised anyone would want to go the other way. Same ecosystem pain, plus you need to start shipping interpreters or containers, plus the language just doesn't really compare.

I know several people who are serious Haskell users despite, like you, using it just for small personal projects. All of them are quite some way down the autism spectrum and will casually toss around Haskell concepts that require about 30 minutes of googling by anyone else present in the conversation to try to understand. Get two or more of them talking to each other and everyone else present is more or less excluded from the conversation... and this is something they're doing just for fun, not because they're paid to do it.

It could just be a coincidence of statistics, but it does cover every Haskell user I know (needless to say, these people are much smarter than I am).

The number one hard working concept in Haskell is parameterisation. A typical Haskell concept is just a concept, not a Haskell concept.
My experience has been that the more type/safety checks the more chances there are of the AI getting stuck into a stupid loop

Because a lot of times it's missing the way of making the needed steps for the conversion (or it's just not obvious)

Sometimes it needs some nudging

Also this comment is a bit generic, it can also apply to cases where it's not an obvious "type check" but a redundancy that needs to exist but the AI can't get around

It makes sense, Haskell is basically just python from the code perspective. If it's faster to generate code than to compile it you might as well just keep generating til it works for your specific task.
You’d be surprised. It works quite well without the static guard rails. But the static guard rails do improve things but not in some extremely obvious way.
I have worked extensively with FP and non FP codebases with LLM. I find my highly type safe FP code works really really well with LLMs
I general I agree with you. I think expressive type systems are superior, and they are even better in the LLM era.

I would quibble though that Python's is actually pretty good at this point, and, despite what the below poster is saying, straight-forward to set up and use. I am still perplexed that the author chose Python over Rust or Scala or TypeScript though, especially given they presumably want to migrate a Haskell codebase.

I'm perplexed by that too. We are migrating from Python to Rust simply because Rust is more suited towards unattended agentic loops, and we want to move in that direction. The results you get from a harness/agent/LLM with Rust are simply better than Python because the agent gets much better feedback from the compiler when it makes dumb mistakes. Python doesn't have anything even close to something like SQLx, which is a natural fit in Rust because of how Rust macros work.
> Python doesn't have anything even close to something like SQLx, which is a natural fit in Rust because of how Rust macros work.

I'd be interested in hearing/discussing more about this. I was very surprised, when I embarked on my side project, that Rust's options for SQL ORMs all seem so weird.

I think what you are referring to is the derivation of FromRow and stuff with SQLx, right?

I don't paticularly want to use an ORM. What SQLx does is a bit different - it statically analyses the SQL in your program against the database and ensures the SQL is valid.

This means you have rock solid assurance your program won't have database syntax errors at runtime. Basically it's just one less thing to worry about, and it's ideally suited for LLM-generated code.

Oh ok, I think I am using it wrong lol

I remember that being one of its "pros" when I was going over options with the LLM, but now, I just have a bunch of raw SQL strings in my codebase and sqlX's main use in that project, off the top of my head, is just instantiating Rust objects from the raw results with `FromRow` (it's probably doing more than I realize; I am not as connected with the code as I want to be, using LLMs to move fast to launch a couple features before revisiting a lot of the mess).

You should read the article before replying.