Live data from Hacker News

TypeScript: Branded Types

prosopo.io

11–20 of 209 posts

Re: TypeScript: Branded Types

#11

I can see this being useful and it seems about as neat a solution as you can currently get in TypeScript as it stands today, but it’s still cumbersome. My feeling is that while you can do this, you’re swimming upstream as the language is working against you. Reaching for such a solution should probably be avoided as much as possible.

I don't see why. I greatly prefer typescript's structural typing for almost everything. But id's in data models are an exception, so I use branding for those. It works perfectly, the only overhead is in the write-once declaration and now I am protected from accidentally using an AccountId where a MemberId was expected, even though they are both just strings.

Yes! I've wanted this in Typescript for ages! Is it possible to brand primitives though?

Re: TypeScript: Branded Types

#12
post #11

Earlier quoted context omitted.

I don't see why. I greatly prefer typescript's structural typing for almost everything. But id's in data models are an exception, so I use branding for those. It works perfectly, the only overhead is in the write-once declaration and now I am protected from accidentally using an AccountId where a MemberId was expected, even though they are both just strings.

Yes! I've wanted this in Typescript for ages! Is it possible to brand primitives though?

Yes you can, it works just the same :-)

Re: TypeScript: Branded Types

#13
post #2

Amazing, we have invented nominal typing in a structural typing system. Sometimes I wonder what kind of programs are written using all these complicated TS types. Anecdotally, we use very simple, basic types in our codebase. The need for tricks like this (and other complicated types) is simply not there. Are we doing something wrong?

I think the example could be better in this article. Let's say you have a function that takes a database id, an email address, and a name. They're all strings. If you pass arguments to this function and you mess up the order for some reason then the compiler has no idea. Hopefully your unit tests catch this but it won't be as obvious. Branded types solve this problem because you can't pass a name string as an argument that is expected to be an email address.

If you argue that this is not a common problem in practice, then I tend to agree. I haven't seen code with mixed up arguments very much, but when it does happen it can have bad consequences. IMO there is not a big cost to pay to have this extra safety. In TypeScript it's ugly, but in other languages that natively support this typing like Scala, Rust, Swift and Haskell, it works nicely.

Re: TypeScript: Branded Types

#15
To me the real benefit of branded types is for branded primitives. That helps you prevent mixing up things that are represented by the same primitive type, like say relative and absolute paths, or different types of IDs.

You really don't need the symbol - you can use an obscure name for the branding field. I think it helps the type self-document in errors and hover-overs if you use a descriptive name.

I use branding enough that I have a little helper for it:

    /**
     * Brands a type by intersecting it with a type with a brand property based on
     * the provided brand string.
     */
    export type Brand = T & {
      readonly [B in Brand as `__${B}_brand`]: never;
    };
Use it like:

    type ObjectId = Brand;
And the hover-over type is:

    type ObjectId = string & {
      readonly __ObjectId_brand: never;
    }

Re: TypeScript: Branded Types

#16
post #8
post #2

Amazing, we have invented nominal typing in a structural typing system. Sometimes I wonder what kind of programs are written using all these complicated TS types. Anecdotally, we use very simple, basic types in our codebase. The need for tricks like this (and other complicated types) is simply not there. Are we doing something wrong?

You've never had values that are only valid within a specific context and wanted to prevent using them incorrectly?

Yeah structural typing assumes that all fields with the same name & type mean the same thing, but that clearly isn't always the case.

Re: TypeScript: Branded Types

#17
post #2

Amazing, we have invented nominal typing in a structural typing system. Sometimes I wonder what kind of programs are written using all these complicated TS types. Anecdotally, we use very simple, basic types in our codebase. The need for tricks like this (and other complicated types) is simply not there. Are we doing something wrong?

Yeah no, IMO this seems totally extraneous and a layer of complexity not worth introducing to any project. I've never encountered a case where I care that I need to differentiate between two types of exactly the same structure, and my gut feeling (without actually prototyping out an example) is that if this actually makes a difference in your code, you should probably be making the differentiation further up in the flow of information...

Re: TypeScript: Branded Types

#20

I can see this being useful and it seems about as neat a solution as you can currently get in TypeScript as it stands today, but it’s still cumbersome. My feeling is that while you can do this, you’re swimming upstream as the language is working against you. Reaching for such a solution should probably be avoided as much as possible.

I don't see why. I greatly prefer typescript's structural typing for almost everything. But id's in data models are an exception, so I use branding for those. It works perfectly, the only overhead is in the write-once declaration and now I am protected from accidentally using an AccountId where a MemberId was expected, even though they are both just strings.

How do ids of different types accidentally get into a place they shouldn't be? Is this simply a case where someone mistakenly passes along a property that happens to be called "id", not noticing it's an account id rather than a member id (as in, an implementation error)?
Post reply on HN