Live data from Hacker News

Branded types for TypeScript

carlos-menezes.com

61–70 of 152 posts

Re: Branded types for TypeScript

#61
Strings might not be the best way of demonstrating nominal typing, since that's already something TypeScript can manage: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAEghgZwB...

Also, since all examples of branded / nominal types in TypeScript use `as` (I assume to get around the fact that the object you're returning isn't actually of the shape you're saying it is...), you should read up on the pitfalls of it:

https://timdeschryver.dev/blog/stop-misusing-typescript-type...

https://www.reddit.com/r/typescript/comments/z8f7mf/are_ther...

https://web.archive.org/web/20230529162209/https://www.bytel...

Re: Branded types for TypeScript

#62
post #26

Earlier quoted context omitted.

The article describes making "number" a different type, not A and B. It's true that making A and B different is a unique problem of TypeScript, but making number a different type is a common issue in many languages.

number is a primitive, branding a primitive can be done like in the example. To brand a class you could also add a private field. Some languages all values are objects and in those languages then the branding argument applies the same way. For languages with nominal typing and primitives you need to box the type yes. Regardless the core of the issue is understanding how structural typing works vs nominal typing

> For languages with nominal typing and primitives you need to box the type yes.

But the compiler can elide the box for you. Haskell and Idris do this.

Haskell's newtype gives a nominal wrapper around a type without (further) boxing at at runtime. It is erased at compile time. Haskell does box their primitives, but via optimization they are used unboxed in some cases (like inside the body of a function). This technique could be applied to a language that doesn't box its primitives.

Idris also does this for any type that is shaped like a newtype (one data constructor, one argument). In that case, both on the scheme and javascript backend, a newtyped Int are represented as "bare" numbers. E.g. with:

    type Foo = MkFoo Int
a `MkFoo 5` value is just `5` in the generated javascript or scheme code.

Re: Branded types for TypeScript

#63
post #56

Earlier quoted context omitted.

Sure! You need a `type` field (or something like it) in TS. You don't need that in a language like F# -- the discrimation occurs strictly in virtue of your union definition. That's what I meant by "native support."

Isn’t it the same in TypeScript? You don’t need an explicit type field.

It depends on what you're trying to achieve. If there are sufficient structural differences, you're fine (`"foo" in myThing` can discrimate) but if two types in your union have the same structure, TS doesn't give you a way to tell them apart. (This relates back to branded types.)

A good example would be `type Money = Dollars | Euros` where both types in the union alias `number`. You need a tag. In other languages, you don't.

Re: Branded types for TypeScript

#64
post #63

Earlier quoted context omitted.

Isn’t it the same in TypeScript? You don’t need an explicit type field.

It depends on what you're trying to achieve. If there are sufficient structural differences, you're fine (`"foo" in myThing` can discrimate) but if two types in your union have the same structure, TS doesn't give you a way to tell them apart. (This relates back to branded types.) A good example would be `type Money = Dollars | Euros` where both types in the union alias `number`. You need a tag. In other languages, yo…

True, although I think that's just missing branded types, not discriminated unions.

Re: Branded types for TypeScript

#65
post #21

It took me so long to fully appreciate TypeScript's design decision for doing structural typing vs. nominal typing. In all scenarios, including the "issue" highlighted in this article there is no reason for wanting nominal typing. In this case where the wrong order of parameters was the issue, you can solve it with [Template Literal Types]( https://www.typescriptlang.org/docs/handbook/2/template-lite... ). See [1]. A…

This example is also an odd choice because... it's not the right way to do it. If you're super concerned about people misusing hashes, using string as the type is a WTF in itself. Strings are unstructured data, the widest possible value type, essentially "any" for values that can be represented. Hashes aren't even strings anyway, they're numbers that can be represented as a string in base-whatever. Of course any such…

> I also don't really get what this branded type adds beyond the typical way of doing it

Your example is a (non-working) tagged union, not a branded type.

Not sure about op's specific code, but good branded types [0]:

1. Unlike your example, they actually work (playground [1]):

  type Hash = string & { tag: "hash" }
  
  const doSomething = (hash: Hash) => true
  
  doSomething('someHash') // how can I even build the type !?!?
2. Cannot be built except by using that branded type -- they're actually nominal, unlike your example where I can literally just add a `{ tag: 'hash' }` prop (or even worse, have it in a existing type and pass it by mistake)

3. Can have multiple brands without risk of overlap (this is also why your "wrap the type" comment missed the point, branded types are not meant to simulate inheritance)

4. Are compile-time only (your `tag` is also there at runtime)

5. Can be composed, like this:

  type Url = Tagged;
  type SpecialCacheKey = Tagged;
See my other comment for more on what a complete branded type offers https://news.ycombinator.com/item?id=40368052

[0] https://github.com/sindresorhus/type-fest/blob/main/source/o...

[1] https://www.typescriptlang.org/play/?#code/C4TwDgpgBAEghgZwB...

Re: Branded types for TypeScript

#66

Strings might not be the best way of demonstrating nominal typing, since that's already something TypeScript can manage: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAEghgZwB... Also, since all examples of branded / nominal types in TypeScript use `as` (I assume to get around the fact that the object you're returning isn't actually of the shape you're saying it is...), you should read up on the pitfalls of it:…

[deleted]

Re: Branded types for TypeScript

#67

Alternatively for the case of id strings with known prefixes, a unique feature of TypeScript is you can use template string literal types: https://www.kravchyk.com/adding-type-safety-to-object-ids-ty...

Note that the prefix was never intended to be looked at as the real problem. That's not a hash function, that's an example hash function because TFA couldn't be bothered to implement a proper one. They're not actually trying to solve the prefix problem.

This is why I always use `Math.random().toString(16)` for my examples :D People often get lost on the details, but they see `Math.random()` and they instantly get it's... well, just a random thing.

Re: Branded types for TypeScript

#68

Earlier quoted context omitted.

> In most languages, doing what this article describes is quite straightforward Well, no. In most languages you wind up making a typed wrapper object/class that holds the primitive. This works fine, you can just do that in TypeScript too. The point of branded types is that you're not introducing a wrapper class and there is no trace of this brand at runtime.

Depends what you mean by "most languages." I think it's clearer to say which languages have zero-overhead custom types. Go has it. Java didn't used to have it so you would use wrapper classes, but I haven't kept up with Java language updates.

Java does not have it yet. Project Valhalla might bring it with Value types.

Re: Branded types for TypeScript

#69

Earlier quoted context omitted.

I read the article, thanks. It makes the claims that (1) a string of a specific form (a hash) could be misused (eg. someone might call toUpper on it) or (2) passed in incorrect order to a function that takes multiple strings. Named parameters / outward-facing labels (Swift) completely solves (2). For (1), the solution is just ugly. Just use the type system in a normal manner and make a safe class “Hash” that doesn’t…

The point of branded types is, among other things, that you do not need to introduce a wrapper class which consumes additional memory.

Really? I'm surprised you mention memory is even a consideration, never even heard it raised as far as typing choices are concerned.
Post reply on HN