Live data from Hacker News

TypeScript: Branded Types

prosopo.io

121–130 of 209 posts

Re: TypeScript: Branded Types

#122

Flow is much better with opaque types. Also nominal types for classes. And correct variance. And adhering to liskov substitution principles. And exact object types. And spread on types matching runtime behavior. And proper no transpilation mode with full access to the language. And has 10x less LoC than ts. ps. before somebody says "flow is dead" have a look at flow contributions [0] vs typescript contributions [1] […

Check out the react codebase, which is presumably the flagship use of flow. It has hundreds of FLOW FIXME annotations. It's easier have a lot of features and small code base if the you don't handle the difficult cases.

Only hundreds? Not bad then.

I expected thousands.

Re: TypeScript: Branded Types

#123

Anyone else sometimes get more sucked in to perfectly typing stuff instead of writing code. I guess working in the pure logic and formalism of types is just addictive. I love it.

Yes, if you do a good job it removes a lot of mental overhead for building the feature or refactoring it later.

Although if you do a bad job it might add a lot...

Re: TypeScript: Branded Types

#124
post #45

Earlier quoted context omitted.

`never` is a problematic field type, at least unless you make efforts (via non-exported symbols, for instance) to make sure the field is inaccessible - `never` is the type of a value that doesn't exist; it's there in the type system to signify an impossible scenario. For instance, a common use-case is to mark the type of a variable after type narrowing has exhausted every possible case. If you assert that your id's a…

Isn't that what you want to signify? It's the intent, and better than asserting that your IDs have a string-valued field that it doesn't. Ideally you could brand with a private field, but we would probably need `typeof class` for that (assuming `typeof class` allows private members. I'm not sure).

The real problem with `never`, or at least the problem that could actually affect you in this scenario, is that it’s assignable to everything. Just like `any`, but even strict lint rules usually won’t catch the type safety it escapes.

If by some mistake your code gets a reference to a field with a `never` type, you’ll never get a type error passing or assigning that reference downstream. That’s relatively trivial to address if you can spot the (edit: runtime) errors it causes directly, but can wreak terrible havoc if it finds a path through several layers of function calls or whatever other indirection before the runtime consequences surface.

Re: TypeScript: Branded Types

#125
post #26

Earlier quoted context omitted.

Ah, yeah the error makes sense. I expected the error, just wanted to understand how Brand was meant to be actually assigned to a primitive. I'm not sure the function is necessary though. This does the same thing const accountId = "125314" as AccountId It makes sense that the technique uses casting.

That's a very clear example. But what if I instead wrote: const accountId = AccountId ("125314" ); There would be the needed checks and balances in the function AccountId(). Wouldn't that do pretty much the same thing?

Yes, but then there would be runtime overhead for the wrapper (presuming a class or object is returned) or the type of accountId would be the underlying type (if AccountId isn't returning a branded type).

Re: TypeScript: Branded Types

#126
post #109

Isn't this just the classic issue of inferred typing coming back to bite us in the way everyone originally predicted? Go runs into the same issue where wildly different types may be considered the same based purely on coincidental naming and matching against interfaces the original authors had no intent to match against. At the end of the day I think the easier system to work with is one in which all type compatibili…

I haven’t run into interfaces coincidentally matching in Go. Have you? It might happen more easily for primitive types, though, and Go does have a way to declare new types:

   type UserId string

Re: TypeScript: Branded Types

#127

Anytime I’ve come across the need to do this, I’ve found a class is a better and less complicated solution. I really like the pattern of value objects from Domain Driven Design. Create a class that stores the value, for example email address. In the class constructor, take a string and validate it. Then anywhere that you need a valid email address, have it accept an instance of the Email class. As far as I understand…

Given a Branded / Nominal interface, they look about the same usage wise but the branded type disappears at runtime (whether that is a benefit or a detriment depends on your use case):

    type Email = Branded

    function Email(maybeEmail: string): Email {
      assertIsEmail(maybeEmail);
      return maybeEmail;
    }

    function assertIsEmail(value: string): asserts value is Email {
      if(!isEmail(value)) throw TypeError("Not an email");
      return value;
    }

    function isEmail(value: string): value is Email {
      return value.contains("@");
    }
Versus:

    class Email {
      #email: string;
      public constructor(maybeEmail: string) {
        if(!isEmail(maybeEmail)) throw TypeError("Not an email");
        this.#email = maybeEmail;
      }
      valueOf() {
        return this.#email;
      }
    }

Re: TypeScript: Branded Types

#128
post #112

Earlier quoted context omitted.

There was plenty of outrage when classes were added to JS. “JS uses prototypal inheritance, not OOP!”

I'm still a bit cranky about that, but because objects should be good enough for anyone. They aren't even really classes anyway.

If it walks like a duck, and quacks like a duck, for all intents and purposes, it is a duck.

Re: TypeScript: Branded Types

#129
post #109

Isn't this just the classic issue of inferred typing coming back to bite us in the way everyone originally predicted? Go runs into the same issue where wildly different types may be considered the same based purely on coincidental naming and matching against interfaces the original authors had no intent to match against. At the end of the day I think the easier system to work with is one in which all type compatibili…

I mean, they have to stay backwards compatible with Javascript. That kind of limits your options.
Post reply on HN