I was wrong about typescript part 1

6 points by amadu


bakkot

This article has several weird misunderstandings of TypeScript. They don't necessarily invalidate the overall point but I'll try to enumerate so that anyone reading isn't misled.

In typecript’s not so strict type system, you can cast anything to the any type. And from the any type, you can cast it to whichever type you wish.

This is true but it's also true of unknown type, which is the type-theoretic "top". TypeScript only lets you do casts directly up and down the type lattice, which is why this works. (Incidentally, this also means you can do as never as Foo, which is not the idiom but works just as well.) The any type is actually much worse than this, because you can use a value of type any as a value of any type without a cast, not merely cast it to anything. any is legitimately bad, but not for this reason.

The compiler does not complain about this, despite it being inherently wrong. This is something that will fail at runtime. If we were to access the id or email property on the returned object, the program would crash.

It is not inherently wrong, it just means that you are explicitly claiming to know better than the type checker. Which is always going to be happen in at least some cases. Also, in this specific example, accessing those properties would give you undefined, not crash.

Here is another example forcing typescript to not be strict. The !! syntax:

The syntax is !, not !!. And as with the previous point, there's nothing wrong with being able to override the type checker.

Let’s take a look at another example.

This example doesn't pass the type checker. In particular, the

function returnSafeUser(user: User): SafeUser {
	return user;
}

doesn't check, because User is not a SafeUser. You can't make this check without actually returning a SafeUser or explicitly overriding the type checker (or exploiting an unsoundness in the type checker, of which there are several). I have no idea what point this section is trying to make.

This is fundamentally different from languages like Rust, where the type system can actually guarantee that if you claim to return an Option<T>, you genuinely can’t return null, the compiler enforces the contract at the language level.

As I understand it, in Rust, like in TypeScript, you can say you know better than the compiler. In Rust this is done with the unsafe keyword, something like

unsafe {
  std::mem::transmute::<*const u8, Option<&'static u8>>(std::ptr::null())
}

(Maybe Option is a bad example because the None representation is actually null? But something like this, anyway.)

This is precisely equivalent to the TypeScript examples, just louder and more discouraged.

rtpg

I often see error handling come up in typescript, and it makes me a bit sad to see that people running into this reach into replicating Result as a wrapped object when hitting these cases.

Typescript's big advantage for "down the line" enterprise-y code for me is how you can do type discrimination just through random property checks. So it's perfectly reasonable to have something like

getUser(id: string): {name: string, email: string} 
                      | {error: "notFound", details: string} { 
 // ...
}

The core point here being that because your result type and error type are shaped differently, the type system is going to help you out, but you also just get back the object you wanted in a successful case.

This does have some nasty footguns (Result<T, E> = T | E means that Result isn't composable like a wrapped type is), but if you're writing application code these are basically not hit just by the fact that it's very rare for some business object to be shaped like your error object.

The main reason I'm against replicating the same ADT structure found in Rust or Haskell is that, unfortunately, TS does end up being JS, a language without pattern matching, without ? shorthands, without a bunch of other stuff... so the ergonomics of pure replication lead to some awkward code. But you can have a smooth API and have TS still help you out a lot anyways! Tagged litteral discrimination means you don't need to wrap things in many cases.