Live data from Hacker News

TypeScript: Branded Types

prosopo.io

181–190 of 209 posts

Re: TypeScript: Branded Types

#181

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…

Classes can also make use of the native `instanceof` JavaScript operator [0]. It is also possible to then infer a type from a class so you can use both the class where you want to discriminate types and the type where you really only care about the shape. The absolutism towards OOP/FP -- instead of embracing the right use cases for each -- always ruffles me in the wrong way. C#, for example, does a great job of blend…

> The absolutism towards OOP/FP

… also doesn’t need to proscribe use or non-use of classes. People were doing OOP in JS long before classes were added to the language. I routinely use classes in a FP style because they’re excellent for modeling value types with a stable structure.

Re: TypeScript: Branded Types

#182
Seems like patching earlier language architecture mistake. If you create custom types, you should not use duck typing on them. You should use typecasting, so the programmer would write something like `result = processEntityA(A(B))`

Re: TypeScript: Branded Types

#183

Earlier quoted context omitted.

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.

It's the other way around. Handling difficult cases leads to more type errors. If type system is lax, it won't flag them but they can fail at runtime. React has very little of $FlowFixMe annotations for its codebase. In typescript projects on the other hand it's normal to see unsafety as normal code (casting, non null assertions, implicit and explicit anys etc).

React vs "typescript projects" doesn't seem very fair. React would presumably be the pinnacle of style with respect to flow. Maybe not true, but it's not just some random project.

Additionally, I've read a lot of typescript code, and the react codebase. My experience is different than yours. I see more type workarounds in react.

FWIW "$FlowFixMe" occurs 822 times in commit hash cf5ab8b8b2c92523aba0b982fb403add053a9110 out of 498900 lines of *.js source. That includes blanks and comments. I don't have any good stats on the occurrence of type assertions in typescript.

Re: TypeScript: Branded Types

#184
post #158

Earlier quoted context omitted.

> And once your own trusted code has made an OrgId, you don't need any runtime checking to see if it actually is an OrgId. Right, and once I have a verified OrgId, I'll just keep using the `myOrgId` variable throughout my code, and I don't really need branding. Maybe I can do type aliasing to make the code easier to read (type OrgId = string), but hardline type verification via branding seems moot unless you can make…

> Right, and once I have a verified OrgId, I'll just keep using the `myOrgId` variable throughout my code, and I don't really need branding. Maybe I can do type aliasing to make the code easier to read (type OrgId = string), but hardline type verification via branding seems moot unless you can make strong runtime guarantees. I mean, don't get me wrong, I think it's a cute novelty, but it doesn't really do anything. I…

> I'd question whether people even need to know OrgId is a string.

I mean, with numbers it's even more confusing (this might be a TS bug?):

    type SpecialNumber = number & { __brand: "SpecialNumber" };
    let n: SpecialNumber = 42 as SpecialNumber;
    
    n++;        // works (but should break)
    n+=1;       // breaks
    n = n + 1;  // breaks
I understand the purpose behind it, I just think it's needlessly confusing and obtuse, and would be curious to see any serious code base that uses branding.

Re: TypeScript: Branded Types

#185
post #175

Earlier quoted context omitted.

It’s very readable to me, and is much cleaner than the notation in the article. Is your problem the line wraps in the parent’s comment?

this is readable? export type Brand = T & { readonly [B in Brand as `__${B}_brand`]: never; }; I count 14 different pieces of punctuation. you might as well use Perl at that point.

It's very readable if you know what every bit means.

    export type Brand = T & {
      readonly [B in Brand as `__${B}_brand`]: never;
    };
It exports a freshly defined type named Brand, which is a generic type with two parameters, one named T (that can be any type), and other named Brand (probably should be named differently to avoid confusion with the name of the whole exported type). Brand parameter must be a type that's assignable to string. For example it can be a type that has only one specific string like "Account" allowed as a value. It might be a bit weird but in places where type is expected "Account" is not treated as string literal but as a type assignable to string with only one allowed value, "Account".

This whole exported type is defined as an intersection type of parameter T and object type that has readonly properties for every type that is assignable to the type passed in as parameter named Brand when this generic type is instantiated. For example if "Account" was passed then B can only be "Account" here, so this object type will only contain a single property. Key of this property will be a string that looks like __Account_brand (if the Brand was "Account") and the type of this property will be never, so nothing is allowed to be assigned to this property.

The result of instantiation of this whole exported generic type will be a type which values can be assigned to variables of type T, but attempt to assign value of type T (or type branded with some other string) to variable of this branded type will result in an error because of missing or mismatched brand property.

This definition might allow for interesting complex branding like:

    let id:Brand;
that can be assigned to variables and parameters of types Brand or Brand or just string.

PS.

Intersection type constructed from multiple types in TS using & symbol is a type which values are assignable to variables of each of the constituent types.

For example value of a type Logger & Printer can be assigned to variables and parameters of type Logger and of type Printer as well.

Re: TypeScript: Branded Types

#186
post #175

Earlier quoted context omitted.

It’s very readable to me, and is much cleaner than the notation in the article. Is your problem the line wraps in the parent’s comment?

this is readable? export type Brand = T & { readonly [B in Brand as `__${B}_brand`]: never; }; I count 14 different pieces of punctuation. you might as well use Perl at that point.

I mean you’re not wrong, this is getting pretty far into the weeds in terms of typescript syntax.

Re: TypeScript: Branded Types

#187
post #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.

Not particularly - they could use distinct tokens (i.e. these brandings) on all types implicitly.

Re: TypeScript: Branded Types

#188
post #158

Earlier quoted context omitted.

> And once your own trusted code has made an OrgId, you don't need any runtime checking to see if it actually is an OrgId. Right, and once I have a verified OrgId, I'll just keep using the `myOrgId` variable throughout my code, and I don't really need branding. Maybe I can do type aliasing to make the code easier to read (type OrgId = string), but hardline type verification via branding seems moot unless you can make…

If you have a function addMemberToOrg(memberId: string, orgId: string), you can accidentally call it like this: addMemberToOrg(myOrgId, myMemberId) and nobody will complain. With branded types, the compiler would mark it as an error.

Function signatures already solve that problem. We have all kinds of functions that take two numbers that mean different things (width/height, x/y, etc.). Branding seems like a solution looking for a problem. I just think it's too much overhead and confusion for too little gain.

In fact, a common pattern is to pass fully-qualified objects, e.g. `dimensions = {width: number, height: number}`, which makes mixing up variables even less likely since you have to explicitly specify them.

Re: TypeScript: Branded Types

#189

Selfish plug about the same topic https://dnlytras.com/blog/nominal-types

I also wrote something like this and came up with something similar to yours: https://kevincox.ca/2023/03/21/lying-to-typescript/#summary

However I just inlined the "tag". Also like yours but unlike OP there is no runtime tag. Which can be a pro (no extra code) or a con (can't do runtime checks).

Re: TypeScript: Branded Types

#190
post #175

Earlier quoted context omitted.

this is readable? export type Brand = T & { readonly [B in Brand as `__${B}_brand`]: never; }; I count 14 different pieces of punctuation. you might as well use Perl at that point.

It's very readable if you know what every bit means. export type Brand = T & { readonly [B in Brand as `__${B}_brand`]: never; }; It exports a freshly defined type named Brand, which is a generic type with two parameters, one named T (that can be any type), and other named Brand (probably should be named differently to avoid confusion with the name of the whole exported type). Brand parameter must be a type that's as…

that is WAY too complicated. not to mention using enough of that probably destroyed compile time/interpretation time. every time I see "advanced type logic" it seems to ruin a language, because people dont know how to use it in moderation.
Post reply on HN