Live data from Hacker News

TypeScript: Branded Types

prosopo.io

161–170 of 209 posts

Re: TypeScript: Branded Types

#161

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 believe what the OP meant by "to get nominal typing in TypeScript" is that the type information is available at runtime through the use of the `instanceof` keyword.

I am surprised your code doesn't give an error, x and y are definitely not the same thing because `x instanceof Email` would be false at the end of that code. But like you said, to TS x and y are indeed the same type because they have the same structure. In practice both can be used interchangeably in the code (even if Email extended another class) with the sole exception of the `instanceof` keyword.

Re: TypeScript: Branded Types

#162

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);

foo.constructor actually doesn’t matter; foo.__proto__ == Foo.prototype is the one that actually makes things chug, and you can change the class of a value by changing its __proto__.

Re: TypeScript: Branded Types

#163

Earlier quoted context omitted.

If we needed a special Typescript runtime to use Typescript it would become nearly useless. The vast majority of Typescript becomes JavaScript running in Node or V8. The web is stuck with JavaScript, and Typescript is a tool to help us write maintainable JavaScript.

Could’t one write a TypeScript runtime in JavaScript or WASM?

Using any JIT language (most implementations of python, Java, ruby, JS, etc) in WASM means porting the runtime to WASM and then having the runtime (in WASM) parse and JIT-compile your code incurring a double-whammy of performance degradation (WASM runtime compiling your JIT language runtime and then your JIT language runtime compiling your actual code).

To do this kind of thing viable there are two ways I can think of:

1) proper ahead-of-time compiler for your language targeting WASM directly. But that means pretty much rebuilding the whole stack for the language as that is a completely different approach.

2) Do something like Android Runtime (ART) does which translates Java bytecode to native machine instructions whenever you install an android app. But in this case translate the bytecode to WASM and do it before distribution. This requires a new compiler-backend which is still quite complex.

Both of these mean you don't have a language runtime at all. There is a reason most WASM stuff you see is in written C/C++/Rust and it is not just the lack of GC.

Re: TypeScript: Branded Types

#164

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; } }

Is this different/better than using `instanceof`?

Re: TypeScript: Branded Types

#166
post #72

Earlier quoted context omitted.

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)?

This kind of mistake is quite easy to make, especially when somebody writes a function that takes several IDs as arguments next to each other. I've seen it happen a number of times over the years

    function foo(customerId, itemId, orderId) {
        if(!customer[customerId]) throw new Error("Customer with id=" + customerId + " does not exist");
        if(!item[itemId]) throw new Error("Item with id=" + itemId + " does not exist");
        if(!order[orderId]) throw new Error("Order with id=" + orderId+ " does not exist");
    }
Just an example of how you can detect errors early with defensive programming.

Re: TypeScript: Branded Types

#167
post #75

Earlier quoted context omitted.

Design doesn’t cover all use cases. There’s nothing weird about wanting different types for “user id” and “organisation id” so that you don’t use the wrong argument by mistake

While you're right that branding makes passing arguments more ergonomic, I will still always be able to do `as OrgId` or `as UserId` so you need to do have some failure handling anyway, unless you're okay with blowing up in the user's face.

you can "x as unknown as Whatever" anywhere in the code as well. Or just use @ts-ignore

You can do this kind of thing in "proper" statically typed languages as well like "(Whatever)((Object)x)

The main problem in TS vs Java for this specific case is that if x is NOT Whatever then you get an error some point later in the code. In Java you would get the error immediately at the casting.

It makes debugging a bit trickier but I don't really run into these kind of problems all that often in my TS code and when I do they are usually easy to track down.

Re: TypeScript: Branded Types

#168

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 i…

As much as I love branding, I would be hesitant to use it for critical math and where you might want to inspect the type at runtime as with money. Branding is easily circumvented, so it's best as a developer hint that helps document APIs and alerts us to common mistakes as an incremental improvement over primitives. For money and similar I would use objects and a custom math library.

You can parse before brand thus enhance and enforce runtime correctness.

Re: TypeScript: Branded Types

#169
post #158

Earlier quoted context omitted.

> My point is that the API consumer does not guarantee types (say it's REST or something), so the assumption that the string you send it will always be the right type (or call it "format") seems like a bad one. The raw data from the API will not have any of your internal types applied to it yet, it'll be raw bytes or typed `string`. So I don't really see the connection between this and "I will still always be able to…

> 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.
Post reply on HN