Live data from Hacker News

Branded types for TypeScript

carlos-menezes.com

111–120 of 152 posts

Re: Branded types for TypeScript

#111

Earlier quoted context omitted.

> In this case where the wrong order of parameters was the issue, you can solve it with Template Literal Types You can solve the issue in this particular example because the "hashing" function happens to just append a prefix to the input. There is a lot of data that isn't shaped in that manner but would be useful to differentiate nonetheless. > And for `hash.toUpperCase()`, it's a valid program. It's odd to try and a…

The real problem is that hashes as strings is wrong. Hashes are typically numbers. Do you store people's ages as hex strings?

> Hashes are typically numbers

If we want to get really pedantic, hashes are typically sequences of bytes, not a single number, so really `UInt8Array` is obviously the best choice here. It wouldn't fix the whole "getting arguments with the same types swapped around" issue though. Without named parameters, you have to pull out some hacks involving destructuring objects or branded types like these.

Re: Branded types for TypeScript

#113
You're going to hate that you did that when you want to write a function that prints out any hash, surely? We once did a similar thing in C++, creating types for values in different units. Complete nightmare, everyone hated it. We went back to using 'double' for all floating point values.

Re: Branded types for TypeScript

#114
post #65

Earlier quoted context omitted.

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…

This is a far better summary of branded types than the top level comment that most people commenting should read before weighing in with their "why not just" solutions.

Re: Branded types for TypeScript

#115

Earlier quoted context omitted.

Runtime. Not compiler.

None of this exists at runtime.

Well, they're talking about alternative solutions that do exist at runtime like wrappers.

Granted, it's hard to know exactly what solutions the person is pitching (the person they're responding to). This person presumably thinks renaming arguments like foo(string:) to foo(hash:) solves branded types. And then they vaguely gesture at other solutions like namespacing and 'safe classes'.

Re: Branded types for TypeScript

#116
I don't understand why users of branded types use strings as brands. Also using a utility type makes unreadable the TypeScript errors related to this type.

If I had to use branded types, I personally would prefer a different approach:

  declare const USER_BRAND: unique symbol
  type User = { name: string, [USER_BRAND]: undefined }
This also allows subtyping:

  declare const PERSON_BRAND: unique symbol
  type Person = { name: string, age: number, [USER_BRAND]: undefined, [PERSON_BRAND]: undefined }

Although this is sometimes convenient, I always find branded types too clever. I would prefer a dedicated syntax for nominal types. I made my own proposal: https://github.com/microsoft/TypeScript/issues/202#issuecomm...

Re: Branded types for TypeScript

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

What you say is true, but after years of working with TypeScript (and about 15 years of Java before that) I'd say that from a purely practical perspective the structural typing approach is much more productive.

I still have PTSD from the number of times I had to do big, painful refactorings in Java simply because of the way strong typing with nominal types works. I still shudder to think of Jersey 1.x to 2.x migrations from many years ago (and that was a PITA for many reasons beyond just nominal typing, but it could have been a lot easier with structural types).

I love branded types (and what I think of their close cousin of string template literal types in TS) because they make code safer and much more self-documenting with minimal effort and 0 runtime overhead.

Re: Branded types for TypeScript

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

What you say is true, but after years of working with TypeScript (and about 15 years of Java before that) I'd say that from a purely practical perspective the structural typing approach is much more productive. I still have PTSD from the number of times I had to do big, painful refactorings in Java simply because of the way strong typing with nominal types works. I still shudder to think of Jersey 1.x to 2.x migratio…

I think proper type inference along with some sort of struct literal syntax could make a lot of these problems go away without the need for structural typing. If you can create structs without explicitly mentioning the type (as long as it can be inferred), then you can use the same syntax for different types as long as their signatures are the same. Structural typing makes it too easy to accidentally pass something that looks like a duck but is actually a goose.

As an example, this syntax should be possible without structural typing or explicitly specifying the type of the value:

    // assuming a type signature of moveToPoint(Point)
    moveToPoint({x: 5, y: 10}) // the struct literal is inferred to be a Point
I believe F# has syntax like this, but it's been a while since I've used it so I don't remember the details.

Re: Branded types for TypeScript

#119
post #58

Earlier quoted context omitted.

> Branding makes structural typing work like nominal typing for the branded type only. That's not quite true. Branding doesn't exist at run time, where as nominal typing usually does at some level. Classes exist at runtime, but most typescript types don't, so unless there's something specific about the shape of the data that you can check with a type guard, it's impossible to narrow the type.

> Classes exist at runtime Not necessarily, depending on the language. Functional languages and system languages such as OCaml, Haskell, Rust, but also C (painfully) and C++ can represent wrapper types within a nominal type system at no runtime cost.

Haskell implements type classes via dictionary passing that don’t always get optimized away by the compiler so it does have a slight runtime cost:

https://okmij.org/ftp/Computation/typeclass.html#dict

In Rust, using trait objects also generates a vtable for dynamic dispatch so in that case traits are not fully erased:

https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/sh...

Re: Branded types for TypeScript

#120

Earlier quoted context omitted.

What you say is true, but after years of working with TypeScript (and about 15 years of Java before that) I'd say that from a purely practical perspective the structural typing approach is much more productive. I still have PTSD from the number of times I had to do big, painful refactorings in Java simply because of the way strong typing with nominal types works. I still shudder to think of Jersey 1.x to 2.x migratio…

I think proper type inference along with some sort of struct literal syntax could make a lot of these problems go away without the need for structural typing. If you can create structs without explicitly mentioning the type (as long as it can be inferred), then you can use the same syntax for different types as long as their signatures are the same. Structural typing makes it too easy to accidentally pass something t…

> Structural typing makes it too easy to accidentally pass something that looks like a duck but is actually a goose.

This may be true, but in reality after 7 years of using TypeScript I don't think I've ever encountered this as a bug for an object (Record) type.

Even for branded types I find the value much more in self-documenting code than actual type safety, and I only have used branded types for primitives.

Post reply on HN