Comment by alprado50

1 day ago

Maybe it is only my experience, but i feel that languages that were not typed since the begining never work as well as "true" typed ones.

Elixir's heavy reliance on pattern matching has always made it kind of "dynamic language where you still have to think about types" vibe to it. It's also always had a spec meta-language (taken from Erlang) which a lot of people use. You should read up on how they have been implementing the type system, it's pretty interesting! I would not say it's "bolted on." It also has full inference so all codebases get the benefit of it whether you specify types or not.

  • Yes, it is what I found works so well. It is easy to write short, specific functions in Elixir and adding Typespecs to theses functions is like typing a block of code. Within the functions everything is "easily" understandable.

    Input > Enumerable.Map(Input, type-speccd functionA) > Enumerable.Map(Input, type-speccd functionB)

Conversely, TypeScript is my favourite type system because it has to support the wild things people did in untyped languages.

  • The issue with TS is that it's not really a type system, it's mostly just comments with a linter bolted on. It tries, but it's fundamentally broken in too many ways.

    Here's just one very simple example, there are many more. I've checked all the strict mode options and this appears to still "typecheck".

      var x: {a: number} = {a: 1};
      var y: {a: number|string} = x;
      y.a = 'FAIL';
      var n: number = x.a; // not actually a number
    

    Source: https://www.typescriptlang.org/play/?noUncheckedIndexedAcces...

    • Two things to note:

      1. TypeScript doesn't aim to have a sound type system. i.e. there may be things the type system accepts that are actually unsafe.

      2. this is more of an issue with mutation. If those properties were marked `readonly`, then the assignment of y.a wouldn't work at all. You can also encapsulate mutation behind functions with your intended types.

      I tend to write TypeScript in a "functional" or "immutable" way, and in this case, most soundness issues come from things like array index access, which can't really be solved without dependent types anyway.

      With that said, TypeScript still gets one quite far *despite* soundness not being a goal of the type system. The problem is that writing imperative, mutable code will make you go through (intentionally!) unsound covariance of types. Similar issues exist for code with side effects, since TypeScript has no way to encode effects in the type system. This is why some language communities settle on ideas like "functional core, imperative shell", where the ultimate goal is absolute minimum amount of code involved in side effects and mutation, while everything else is designed to be easy to test (and, ideally, expressible with a sound subset of your type system).

  • Haha, it is actually my least favorite statically typed lang for this very reason.

    • I agree. The sheer amount of flexibility it provides makes it both hard to use/read and also not particularly safe/sound. No other type system in existence allows you to be as incoherent as TS.

  • You didn't like Purescript? It looked pretty cool to me. Its main competition back in the day was Elm, but Typescript has now taken over. From a distance Typescript seems to have too many gaps. I haven't used it though.

    • I think TypeScript can feel like there's too many gaps because not enough people take it seriously enough to truly learn it. Hardly anyone reads a book about best practices/design the way many do about C/Java/Rust.

      It's actually a very powerful tool when used thoughtfully. Although it wasn't the first structurally typed language I tried, it's the one that made me fall in love with structural type systems

      2 replies →

I think Elixir is taking a very mature path to typing. No type-annotations (yet) just type inference from existing language constructs like function guards and pattern matching. Also trying to minimize false positives, only giving type errors when it would provably crash at runtime.

I've experienced this, but it's mostly because languages like Python and TypeScript give you way too many escape hatches. I get the intent: allow devs to convert their code base slowly. But in practice it just lets developers opt out of the benefits of typing to "save time" in the short run.

  • Once you are squarely in a Typescript program and not a "Javascript program gradually adopting Typescript", it would be a good idea to enable Strict mode which forbids implicit-any, effectively meaning the only places you can omit type declarations is where the language will infer the type. Typescript for instance does not infer types of function arguments via their usages (like Flow does), which means in strict mode you must explicitly provide a type for all arguments within a function declaration.

    I used to be a bit of a pragmatist when it comes to strict mode, but over the years that has subsided, nowadays I think it is plainly obvious that all Typescript programs should use strict mode unless there's a damn good reason. And I'm not sure there are any legitimate damn good reasons.

    True there is no ability to forbid an explicit-any type declaration, though.

  • I’ve never had a real problem with developers opting out. It’s not that hard to enforce coding standards.

    The real problem with Python is the inexpressiveness of its type system and the mess of typed dicts, dataclasses and pydantic classes.

    TypeScript may fail narrowing here and there or require a superfluous assert, but usually writing properly typed code, especially with zod, is the path of least resistance.

  • Well now Claude will add the types for me, so I don't need to use escape hatches

    • As long as you're fine with the types being semantic gibberish because all agents I've used take the lowest effort approach to make the error go away.

      You probably have the same logical type duplicated in 3+ different places (at least partially), including inline casts using type literals like "maybeCat as { meow(): void }"

      1 reply →

It also takes a long time for the ecosystem to catch up. It can be hard to retrofit static types over something that wasn’t built with them in mind

  • I keep getting baited by these comments so this is the last one I'll respond to, lol.

    Elixir is always been sort of a "typed dynamic language" due to how baked in pattern matching is. Any good Elixir developer has always been thinking about types anyway, it's almost impossible not to.

So JavaScript didn't work well and is successful?

  • To be fair I think the success of JS is in spite of it not working super well

    • JS was designed well. Got a lot of things right that others copied later, and also made improvements without breaking compatibility. And the random weird things like [] == 0 don't come up much in actual usage.

It was poorly bolted on in Python. Well I dislike types to begin with, but aside from that, Typescript somehow did it better.

  • > Typescript somehow did it better

    I don’t think JavaScript’s syntax was ever designed with the idea that TypeScript would one day exist. Yet somehow it feels like it left the perfect open spaces for TS to later occupy.

    • They did get lucky with that. The Python type syntax ended up being similar, but the implementation of type-checking is confusing, also it was annoying how you needed to import the types of basic collections for a while.

Typescript is brilliant and should be carefully studied by anybody introducing a type system to a single typed ("untyped") language.