← Back to context

Comment by smartmic

2 days ago

From the article:

> I am also deeply sick and tired of maintaining large Python scripts recently, and crave the modularity and type safety of OCaml.

I can totally relate. Switching from Python to a purely functional language can feel like a rebirth.

While python isn't type safe, you can use Pylance or similar in combination with type hinting to get your editor to yell at you if you do something bad type-wise. I've had it turned on for a while in a large web project and it's been very helpful, and almost feels type-safe again

  • It just isn't good enough. Anytime Pyright gives up in type checking, which is often, it simply decays the type into one involving Any/"Unknown":

    Without strict settings, it will let you pass this value as of any other type and introduce a bug.

    But with strict settings, it will prevent you from recovering the actual type dynamically with type guards, because it flags the existence of the untyped expression itself, even if used in a sound way, which defeats the point of using a gradual checker.

    Gradual type systems can and should keep the typed fragment sound, not just give up or (figuratively) panic.

    • Personally I’ve handled this by just ignoring the gradual part and keeping everything strictly typed. This sometimes requires some awkwardness, such as declaring a variable for an expression I would otherwise just write inline as part of another expression, because Pyright couldn’t infer the type and you need to declare a variable in order to explicitly specify a type. Still, I’ve been quite satisfied with the results. However, this is mostly in the context of new, small, mostly single-author Python codebases; I imagine it would be more annoying in other contexts.

  • > I've had it turned on for a while in a large web project and it's been very helpful, and almost feels type-safe again

    In my experience "almost" is doing a lot of heavy lifting here. Typing in python certainly helps, but you can never quite trust it (or that the checker detects things correctly). And you can't trust that another developer didn't just write `dict` instead of `dict[int, string]` somewhere, which thus defaults to Any for both key and value. And that will type check (at least with mypy) and now you lost safety.

    Using a statically typed language like C++ is way better, and moving to a language with an advanced type system like that of Rust is yet another massive improvement.

    • Yeah, if you're going to use static type checks, which you should, you really want to run the checker in strict mode to catch oversights such as generic container types without a qualifier.

      Although I've found that much of the pain of static type checks in Python is really that a lot of popular modules expose incorrect type hints that need to be worked around, which really isn't a pleasant way to spend one's finite time on Earth.

      1 reply →

    • dict types to dict[Unknown, Unknown], not dict[Any, Any]. I'm the main developer on this code right now, so I've been pretty much ensuring its all typed reasonably well myself, but I don't just blindly trust it. I check what types it thinks it is, reason why they aren't as narrow as they could be, and fix that to make them more narrow, sometimes introducing extra classes so I don't have to type them as dict[dict[dict...]]. This is also an established codebase that does server-side processing that flask makes easy, and most of the developers working on it don't know C++ or Rust

OCaml isn't pure.

  • (author here) it's actually the module system of OCaml that's amazing for large-scale code, not the effects. I just find that after a certain scale, being able to manipulate module signatures independently makes refactoring of large projects a breeze.

    Meanwhile, in Python, I just haven't figured out how to effectively do the same (even with uv ruff and other affordances) without writing a ton of tests. I'm sure it's possible, but OCaml's spoilt me enough that I don't want to have to learn it any more :-)

  • I recently realized that "pure functional" has two meanings, one is no side-effects (functional programmers, especially of languages like Haskell use it this way) and the other is that it doesn't have imperative fragments (the jump ISWIM to SASL dropped the non-functional parts inherited from ALGOL 60). A question seems to be whether you want to view sequencing as syntax sugar for lambda expressions or not?