Hacker News new | ask | show | jobs
by StevenWaterman 2283 days ago
TypeScript is surprisingly good at this. It's not popular as a backend language, but shows that your choices aren't limited to pure FP languages and low-level systems programming languages.
1 comments

Typescript is explicitly bad at this; it is structurally typed, not nominally typed, meaning its effectively useless at enforcing domain guards.

See the same program in flow [1] (nominally typed) and TypeScript [2] (structurally typed).

In the case of flow the type can only be constructed with the class -thus enforcing the guards - whereas in TypeScript I can accidently (or deliberately) bypass all guards by having a class with an equivalent structure.

[1] https://flow.org/try/#0MYGwhgzhAEAKD2ECWAXJA3ApgSQHYswHNMAna...

[2] https://typescript-play.js.org/#code/MYGwhgzhAEAKD2ECWAXJA3A...

There are ways to enforce nominal typing in TS [1]

  type Brand<K, T> = K & { __brand: T }

  type USD = Brand<number, "USD">
  type EUR = Brand<number, "EUR">

  const usd = 10 as USD;
  const eur = 10 as EUR;

  function gross(net: USD, tax: USD): USD {
    return (net + tax) as USD;
  }

  gross(usd, usd); // ok
  gross(eur, usd); // Type '"EUR"' is not assignable to type '"USD"'.
[1] https://michalzalecki.com/nominal-typing-in-typescript/#appr...
Thats pretty cool, but it comes with a series of issues:

* It uses an arguably invalid construct "K & { __brand: T }", where K is not an object, is an empty intersection. The fact that typescript allows casting a number to this type is concerning.

* Typescript currently allows "{} as USD" for non-object "K"'s but this will throw up serious issues down the line (obj is not number etc.); this is a likely error after validating JSON for example.

* Similarly typescript will allow you to bypass the guards for primitive types by using the structurally invalid value "{__brand: 'USD'}", or bypass them for object types using a structurally valid form with a "__brand: 'USD'" member. Which is more concerning I don't know.

* The type system now believes you have a member "__brand" that you don't actually have.

* In summary, you cannot enforce the guards through the type system.

That said, this is an interesting hack that, assuming your developers aren't trying to hurt you and you don't use it for primitives, could help get some extra safety in there. However the absurdity of the intersection looms heavily over it, I wouldn't bet on this working in a few years...