Live data from Hacker News

TypeScript: Branded Types

prosopo.io

111–120 of 209 posts

Re: TypeScript: Branded Types

#111
post #47

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…

do you need readonly with never?

Yes, otherwise consumers could try reading the property (that doesn’t exist).

Re: TypeScript: Branded Types

#112

Earlier quoted context omitted.

That's a weird objection, because Typescript classes are literally Javascript classes[1]. [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

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.

Re: TypeScript: Branded Types

#113

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…

> Anytime I’ve come across the need to do this, I’ve found a class is a better and less complicated solution ... As far as I understand classes are the only real way to get nominal typing in TypeScript. How are classes going to help? As far as I understand TS is structural period. Ex this is valid (!): class Email { constructor(public email: String) {} } let x: Email = new Email('foo@foo.com') let y = { email: '73'}…

I had the same initial reaction, but it turns out that if you have at least one private member, even with the same signature, it works as the parent comment suggests.

For example, these two classes are distinct to TypeScript:

    class Name {
        constructor(private value: string) {}

        getValue() {
            return this.value;
        }
    }

    class Email {
        constructor(private value: string) {}

        getValue() {
            return this.value;
        }
    }

    const email: Email = new Name(“Tim”); // Error: (paraphrasing) conflicting private declaration of “value”.

Re: TypeScript: Branded Types

#114

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…

Yeah, classes are generally better than branded types for objects (unless you're just building discriminated unions). What's particularly better these days is that you get nominal typing and brand checks with standard private fields: class Foo { #brand; static isFoo(o): o is Foo { return #brand in o; } }

This is really neat, thank you for sharing!

Re: TypeScript: Branded Types

#115
post #26

Earlier quoted context omitted.

This error is, in fact, the point. It keeps you from accidentally assigning a normal string to a branded string You have to make a function to apply the brand via a cast, the article explains this as well. function makeObjectId(id: string): ObjectId { return id as ObjectId; }

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?

Re: TypeScript: Branded Types

#116

Earlier quoted context omitted.

> Anytime I’ve come across the need to do this, I’ve found a class is a better and less complicated solution ... As far as I understand classes are the only real way to get nominal typing in TypeScript. How are classes going to help? As far as I understand TS is structural period. Ex this is valid (!): class Email { constructor(public email: String) {} } let x: Email = new Email('foo@foo.com') let y = { email: '73'}…

I had the same initial reaction, but it turns out that if you have at least one private member, even with the same signature, it works as the parent comment suggests. For example, these two classes are distinct to TypeScript: class Name { constructor(private value: string) {} getValue() { return this.value; } } class Email { constructor(private value: string) {} getValue() { return this.value; } } const email: Email…

You’re right. This is what I meant, but I was not at all clear in my original comment.

Re: TypeScript: Branded Types

#117

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…

> As far as I understand classes are the only real way to get nominal typing in TypeScript. Although classes and instances are ultimately structural, right? let foo = new Foo(); assert(foo.constructor == Foo);

Yep, you’re absolutely right

Re: TypeScript: Branded Types

#119
An example of what you can implement on top of branded types that I want to share with fellow hackers:

- currencies

You may have some sort of integer representing the number of cents of some currency and you want to avoid doing operations between the wrong currencies (such as adding euros and pesos).

You can create branded types and functions that work on those Euro branded numbers and then decide how to do math on it. Or numbers representing metals and such.

It's useful in other scenarios such as a, idk, strings, you could theoretically brand strings as idk ASCII or UTF-8 or the content of an http body to avoid mixing those up when encoding but I want to emphasize that often many of those hacks are easier to be handled with stuff like dictionaries or tagged unions.

An example of what can be achieved with similar approaches (beware it's oriented for people that are at least amateur practitioners of functional programming) is Giulio Canti's (an Italian mathematician and previously author of t-comb, fp-ts, io-ts, optic-ts, and now effect and effect/schema), the money-ts library:

https://github.com/gcanti/money-ts

Re: TypeScript: Branded Types

#120
post #24

Earlier quoted context omitted.

Sorry, how do you apply it to branding primitives? The basic const accountId: Brand = "123654" Has the error Type 'string' is not assignable to type '{ readonly __Account_brand: never; }'

This error is, in fact, the point. It keeps you from accidentally assigning a normal string to a branded string You have to make a function to apply the brand via a cast, the article explains this as well. function makeObjectId(id: string): ObjectId { return id as ObjectId; }

Which, in real life, makes you parse (and handle or throw errors or log, whatever you want) but then now that the item you have is what you need.

Another way you can implement the same: through a class constructor like new UserId('some string') which can also throw or let you handle operations on the class itself while allowing you to get the value.

Post reply on HN