Live data from Hacker News

TypeScript: Branded Types

prosopo.io

31–40 of 209 posts

Re: TypeScript: Branded Types

#31

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…

Yeah in TS' own playground example they don't create a Symbol, they just intersect it inline: https://www.typescriptlang.org/play#example/nominal-typing

Nice.

I don't love that example though, because the brand field's value of a string literal will make it seem like the object actually has a property named `__brand` when it doesn't. `never` is the best brand field type as far as I can tell.

Re: TypeScript: Branded Types

#33

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.

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

Yeah, or what's more likely is that you originally used an AccountID everywhere, but then you decided that you want to use a UserAccountID. So you update your functions but you miss a spot when you're updating all your call sites.

Or you have a client library that you use to interact with your API, and the client library changes, and you don't even notice.

Or you change the return type of a function in a library, and you don't even know who all the callers are, but it sure would be nice if they all get a build error when they update to the latest version of your library.

Lots and lots and lots of ways for this to happen in a medium+ sized project that's been around for more than a few months. It's just another way to leverage the power of types to have the compiler help you write correct code. Most of the time most people don't mess it up, but it sure feels good to know that it's literally impossible to mess up.

Re: TypeScript: Branded Types

#34
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.

The function is great in cases where you can validate the string, or paired with lint rules that limit casting.

Re: TypeScript: Branded Types

#35
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]

[0] https://github.com/facebook/flow/graphs/contributors

[1] https://github.com/microsoft/TypeScript/graphs/contributors

Re: TypeScript: Branded Types

#36

It's a clever trick, but the compiler errors leave a lot to be desired. If a TS library makes heavy use of nominal (branded/distinct) types in a domain where accidentally passing values of the wrong type is common, I can imagine a lot of library users being more confused, not less, by code that uses this approach. The article reads more like an endorsement of languages that do structurally-aware nominal typing (that…

This is one of those frustrations I encounter with C++.

C++'s templating lets express some very powerful type constraints... But good luck reading the compiler errors generated by someone else's very powerful type constraints that you haven't fully grokked to an implementation level.

Re: TypeScript: Branded Types

#37

It's a clever trick, but the compiler errors leave a lot to be desired. If a TS library makes heavy use of nominal (branded/distinct) types in a domain where accidentally passing values of the wrong type is common, I can imagine a lot of library users being more confused, not less, by code that uses this approach. The article reads more like an endorsement of languages that do structurally-aware nominal typing (that…

Generally speaking, I wouldn't use branded inputs for libraries. Branded types make a lot more sense to me when working in business-logic cases, to identify data at the edge of a bounded context and tracing through the system. A library is downstream of that and the code that requires the branded type should be controlling the inputs to the library.

Re: TypeScript: Branded Types

#38
post #22

Very useful for DDD, like having an Email type, or String100 (string of 100 characters)

Works very well too for any kind of validation or encoding. Anything that accepts input from the outside world can accept a string. And then everything else in the app can work with a "SafeString" and the only way to create a safe string is to send a string through a string escape function (or whatever makes sense for your app).

Works especially well if you're using any kind of hexagonal architecture, make your functional core only accept validated/escaped/parsed/whatever types, and then the imperative shell must send any incoming data through whatever transformation/validation/etc before it can interact with the core.

Re: TypeScript: Branded Types

#39

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] […

FWIW, classes in TypeScript become nominal types when they have a non-public member. But I definitely do feel real FOMO over Flow’s opaque types.

Re: TypeScript: Branded Types

#40

Earlier quoted context omitted.

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

That’s just plain encapsulation, if I understand you correctly. Branding, on the other hand, prevents complex types from being confused.

Not really, not for TS at least. If you just want to take a string and call it an email address and have your function only accept email addresses (the simplest use case) then you need to use branding. That's not encapsulation.
Post reply on HN