Remix.run Logo
Tade0 4 days ago

Regarding TypeScript:

> but types lie at runtime (unsound) requiring you to add your own runtime checks.

I don't recall doing that outside of situations when data is crossing system boundaries.

The vast majority of languages have an unsound type system, yet people are productive in them.

Over the years I've come to realise it's a form of nitpicking. You absolutely need it in some applications, but it's not an important requirement most of the time and it's given undue importance by some.

jim33442 3 days ago | parent | next [-]

I agree. Ironically have inherited a lot of projects where they picked a "type-safe" language but didn't properly type the system boundaries, including RPC and database. Wrong priorities there.

tracker1 2 days ago | parent [-]

Adjacent to the article... there are a lot of bits from WCF I really liked and miss. I didn't really like a lot of the default way to use it... I generally would combine all the interfaces and a client instance generator into a single library to share (as opposed to the XML config crap the people tended to default with).

Today, there's tooling around OpenAPI/Swagger, but I don't think it's nearly as nice as using WCF for the server and client interfaces directly.

teaearlgraycold 4 days ago | parent | prev [-]

The main problem with TypeScript is the default compiler flags are not safe. I understand having things weak when you’re getting the ecosystem up and running, before you have wide adoption. You don’t want to throw too many red squigglies at early adopters and cause them to give up. But these days the default should not allow any. No implicit any. No explicit any. The stdlib should not use it. JSON.parse() should return either unknown or a recursive union that defines all valid JSON structures.

I believe it’s largely because of these defaults that the idea that TypeScript isn’t really checking types persists. Without the any type, or loose undefined and null checks, your types are as validated as they can be. The only failure point is deserialization or an import which doesn’t type check. And deserialization has this problem in every language.

When you compile C to machine code the executed binary isn’t checking types. This is no different from Typescript that’s been compiled to JavaScript.

sargunv 4 days ago | parent | next [-]

Even with strict flags on, there are failures. A trivial example:

  function mutateArray(
    arr: (string | number)[]
  ) {
    arr.push(1);
  }
  const a: string[] = ["one", "two"];
  mutateArray(a);
a is now a string[] with a number inside
stratos123 3 days ago | parent | next [-]

Woah, that's quite an issue. The equivalent code in Python doesn't typecheck, since `list` is invariant and hence list[str] doesn't fit list[str|int]. Unusual for TS to handle types worse than Python.

teaearlgraycold 4 days ago | parent | prev | next [-]

Very interesting. I’m shocked the typescript creators built a system with this failure mode. I guess the solution here is to have tsc change the type of “a” after the call to mutateArray, unless the arr argument is marked as readonly.

Is there a name for this failure mode?

sally_glance 4 days ago | parent | next [-]

I think the problem is the union argument type - intuitively we read "array of strings OR numbers", but actually it means "array of strings AND numbers". Probably generics would be more appropriate here, with the type param constrained to string or number. Then tsc would also complain about pushing a number without checking the item type before.

fainpul 4 days ago | parent [-]

If it's an "array of strings AND numbers", then it should not be allowed to call the function with a string[], because those are different types.

saghm 3 days ago | parent | next [-]

That would be the sound way to do things, but it's also surprisingly common for arrays for some reason. It also doesn't get checked compile time in Java, although it does throw an exception because the arrays are type-enforced at runtime at least.

This compiles fine:

    class A {}

    class B1 extends A {}
    class B2 extends A {}

    public class MyClass {
        public static void main(String args[]) {
            A[] array = new B1[1];
            array[0] = new B2();
        }
    }
clownstrikelol 3 days ago | parent | prev [-]

That’s barely scratching the surface.

In TypeScript the following is also valid:

class Something{

    value: Number
}

class SomethingElse{

    value: Number

    foo: String

    bar: SomeOtherThing[]
}

function AddSomething(v: Something)

{

    v.value += 1;
}

var ex = new SomethingElse{

    value: 3,

    foo: “TypeScript is fake types”,

    bar: []
};

AddSomething(ex);

Why does it work? Because in TypeScript as long as you have a “shape” that fits, it’s the “same type.”

Complete and utter insanity to me, to pretend there’s any real type checking, when types are entirely fake and made up.

saghm 3 days ago | parent [-]

That's because it's structurally typed (as opposed to nominally typed). I don't happen to prefer it, but I don't think it's fair to conflate that with unsoundness like the example given above; it's totally possible to have a sound structural type system. TypeScript doesn't happen to be sound, but it's not because of that.

teaearlgraycold 3 days ago | parent [-]

Structural typing is great. It's the verified version of duck typing.

LelouBil 3 days ago | parent [-]

You can even get nominal typing with branded types if you need it. (Like for the newtype pattern)

rav 3 days ago | parent | prev | next [-]

In TypeScript it's called "bivariance", which sounds very programming language theory like, but is not a term typically used in academic contexts, since it is unsound almost by default. It's described here: https://www.typescriptlang.org/docs/handbook/type-compatibil...

drzaiusx11 3 days ago | parent [-]

Key sentence in their justification seems to be:

"allowing this enables many common JavaScript patterns"

Honestly at this point they should make a new strict "no js" mode, as the ecosystem likely has reached a tipping point where you can get by without mixing typed and untyped js at compile time. Wonder if targeting wasm directly would help ensure those boundaries are ensured...

drzaiusx11 3 days ago | parent [-]

https://www.assemblyscript.org/ is pretty darn close, but it would probably be better to converge and not split communities

sargunv 4 days ago | parent | prev | next [-]

I'm not aware of a name, but I'm also curious if there is one because I had a hard time searching for it.

I came across it on ThePrimeagen's YouTube channel: https://youtu.be/u1WmiqlrqL0

teaearlgraycold 4 days ago | parent [-]

I asked an LLM and it described the problem as "covariant typing of mutable collections" or "unsound covariance". I'm not mathematically educated in this area but that sounds right?

saghm 3 days ago | parent [-]

Yes, that's correct. If you're curious for a slightly more concrete source, Wikipedia covers this reasonably well (although citing one often decried source to validate another might not be particularly convincing to some people): https://en.wikipedia.org/wiki/Type_variance#Arrays

4 days ago | parent | prev [-]
[deleted]
drzaiusx11 3 days ago | parent | prev | next [-]

Yikes. I've only been using ts for about a year, I had no idea this was considered a "valid" case. Seems like a type error to me. I wonder how they justify this?

4 days ago | parent | prev [-]
[deleted]
Tade0 4 days ago | parent | prev | next [-]

> You don’t want to throw too many red squigglies at early adopters and cause them to give up.

That's not the reason.

TypeScript's main design goal was to enable developers to gradually introduce types in codebases that spent years being written purely in JS.

There's still demand for this feature and those who start off with TypeScript set their own config anyway.

I've dealt with people using all kinds of escape hatches, but 2/3 of the time it's caused by lack of proficiency in the language.

The rest is either someone being in a hurry or just not serious about their job.

satvikpendem 3 days ago | parent | prev [-]

With TypeScript 6.0 and above it is indeed much stricter by default.