Live data from Hacker News

Branded types for TypeScript

carlos-menezes.com

21–30 of 152 posts

Re: Branded types for TypeScript

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

And for `hash.toUpperCase()`, it's a valid program. TypeScript is not designed to stop you from using string prototype methods on... strings!

It's more pronounced in object types that some library authors don't want you to pass an object that conforms to the required shape and insist on passing result of some function they provide. e.g. `mylib.foo(mylib.createFooOptions({...})`. None of that is necessary IMO

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

Re: Branded types for TypeScript

#23

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

Isn't there a risk with this approach that you may receive input with a repeated prefix when there's a variable of type `string` and the prefix is prepended to satisfy the type checker without checking if the prefix already exists?

Re: Branded types for TypeScript

#25
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…

How do you do this with template literal types? Does that mean you changed the string that gets passed at runtime?

The nice thing about branding (or the "flavored" variant which is weaker but more convenient) is that it's just a type check and nothing changes at runtime.

Re: Branded types for TypeScript

#26

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.

I see where you are coming from but you are not quite understanding what the OP was saying class A { public value: number } class B { public value: number } const x: A = new B() // no error This is structural typing (shape defines type), if typescript had nominal typing (name defines type) this would give an error. You could brand these classes to forcefully cause this to error. Branding makes structural typing work…

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.

Re: Branded types for TypeScript

#27
Interesting way of elevating bug issue to compile time. I'll definitely try to apply it to my TypeScript Front-End.

I use the newtype pattern a lot in Rust. I try to avoid passing strings around. Extracting information over and over is cumbersome. Ensuring behavior is the same is big-ridden.

An example: is a string in the email address format? Parse it to an Email struct which is just a wrapper over String.

On top of that we then assign behavior to the type, like case insensitive equality. Our business requires foo@BAR.com to be the same as FoO@bAr.com. This way we avoid the developer having to remember to do the checks manually every time. It's just baked into the equality of the type.

But in Rust using

    type Email = String;
just creates an alias. You really have to do something like

    struct Email(String)
Also, I know the only way to test an email address is to send an email and see if the user clicks a link. I reply should introduce a trait and a ValidatedEmail and a NotValidatedEmail.

Re: Branded types for TypeScript

#28
post #3

In most languages, doing what this article describes is quite straightforward: you would just define a new type (/ struct / class) called ‘Hash’, which functions can take or return. The language automatically treats this as a completely new type. This is called ‘nominal typing’: type equality is based on the name of the type. The complication with TypeScript is that it doesn’t have nominal typing. Instead, it has ‘st…

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

Re: Branded types for TypeScript

#29

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.

I see where you are coming from but you are not quite understanding what the OP was saying class A { public value: number } class B { public value: number } const x: A = new B() // no error This is structural typing (shape defines type), if typescript had nominal typing (name defines type) this would give an error. You could brand these classes to forcefully cause this to error. Branding makes structural typing work…

You can fix that fairly easily using private variables:

  class A {
    private value: number
  }
  class B {
    private value: number
  }
  const x: A = new B() // error
You can also use the new Javascript private syntax (`#value`). And you can still have public values that are the same, so if you want to force a particular class to have nominal typing, you can add an unused private variable to the class, something like `private __force_nominal!: void`.

Re: Branded types for TypeScript

#30

Interesting way of elevating bug issue to compile time. I'll definitely try to apply it to my TypeScript Front-End. I use the newtype pattern a lot in Rust. I try to avoid passing strings around. Extracting information over and over is cumbersome. Ensuring behavior is the same is big-ridden. An example: is a string in the email address format? Parse it to an Email struct which is just a wrapper over String. On top of…

Scala 3 has opaque types for this. And libraries like iron build on top of it so you can have a very clean way of enforcing such things at compiletime.
Post reply on HN