Live data from Hacker News

TypeScript: Branded Types

prosopo.io

81–90 of 209 posts

Re: TypeScript: Branded Types

#81

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…

Classes can also make use of the native `instanceof` JavaScript operator [0].

It is also possible to then infer a type from a class so you can use both the class where you want to discriminate types and the type where you really only care about the shape.

The absolutism towards OOP/FP -- instead of embracing the right use cases for each -- always ruffles me in the wrong way. C#, for example, does a great job of blending both OOP and FP (borrowing heavily from F# over the years). JS and by extension TS has the same flexibility to use the right paradigm for the right use cases, but it seems that everyone wants to be on one end of the spectrum or the other instead of accepting that JS is an amalgamation.

Evan You had a great quote on this where he correctly calls out much of the complexity and performance issues with React as being rooted in pushing against the language rather than embracing it.

[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Re: TypeScript: Branded Types

#82
post #48

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

> adhering to liskov substitution principles what does this even mean? > And has 10x less LoC than ts Prolly b/c Flow isn't able to express the advanced types (albeit with 10x LoC) in the first place.

You can google, gpt or look at wikipedia for "liskov substitution principles". It's related to object oriented programming, more specifically to inheritance and what is allowed as substitution as superclass vs subclass depending on which position it sits in argument vs return value. It's very interesting read if you don't know about it and you're using OOP.

What advanced types do you have in mind?

ps. the way you're using "albeit" sounds like you think flow has 10x larger codebase, it has 10x smaller codebase

Re: TypeScript: Branded Types

#83
post #75

Earlier quoted context omitted.

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.

The way you're describing it sounds to me like you are adding failure handling to handle cases where the programmer is intentionally misusing the thing. I would argue that in this type of situation, the error handling is not necessary because it would hide the fact that the thing is being misused. Or perhaps I'm misunderstanding your comment. When you do `as OrgId` or `as UserId`, where do you envision those casts in…

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. Unless you control API usage from end-to-end (which kind of defeats the point of an API), you need error checking (or at least exceptions to bubble up).

A lot of times branding is used to mark data received from a database, e.g.: this field is an `OrgId`, but I can do all kinds of things to that string which might make it not-an-`OrgId` at any point. Then, I'll try to reuse it the `OrgId`, and I'll get some weird error or blowup and I'll have no idea why. So the point is that (a) branding is a dubious feature because it can obfuscate soft-type-breakage (I call it "soft" because at the end of the day, we're just dealing with strings), and (b) it still doesn't preclude runtime error checking unless you're okay with blowups.

Re: TypeScript: Branded Types

#85

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…

Classes can also make use of the native `instanceof` JavaScript operator [0]. It is also possible to then infer a type from a class so you can use both the class where you want to discriminate types and the type where you really only care about the shape. The absolutism towards OOP/FP -- instead of embracing the right use cases for each -- always ruffles me in the wrong way. C#, for example, does a great job of blend…

Classes are very underutilised in TypeScript. I recently introduced them to our codebase at my day job and got a fair bit of pushback because it wasn’t “JavaScripty” enough.

Re: TypeScript: Branded Types

#86

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

Re: TypeScript: Branded Types

#87
post #2

Amazing, we have invented nominal typing in a structural typing system. Sometimes I wonder what kind of programs are written using all these complicated TS types. Anecdotally, we use very simple, basic types in our codebase. The need for tricks like this (and other complicated types) is simply not there. Are we doing something wrong?

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…

> because you can't pass a name string as an argument that is expected to be an email address

Unless you accidentally create the wrong branded type? Which is as likely as disordered arguments.

As you stated, tests should cover this case trivially, I don't see the value in added type complexity.

Re: TypeScript: Branded Types

#88
post #54

Weird idea, as types in TS are structural by design . If this is something you need, it smells like "runtime checking" not amending the type system.

> If this is something you need, it smells like "runtime checking" not amending the type system.

This is both! The primary point of branded types is to allow you to use the type system to ensure that a particular runtime check has taken place.

Re: TypeScript: Branded Types

#89

Earlier quoted context omitted.

I mean. I think this specific case is a lot more principled than you seem to think. It’s certainly well reasoned from a perspective of structural typing by default.

Yes, they have their reasons. They always do, tradeoff etc, I know. It doesn't change the fact that ie. adding private member is breaking change in your library which is kind of funny (until it's not funny of course). Also stuff like: class Foo { private foo = 1 } class Bar {} const a: Bar = new Foo ...typechecks so that's it for nominality. It's all ifs all the way down.

It’s only a breaking change if you’ve been using a class to stand in for an interface (happens to the best of us I’m sure!). You can still avoid it being one after the fact by introducing a non-breaking interface consistent with what you were already accepting/expecting.

And yeah, I’m not a fan of that class instance assignability case. Not to make excuses for it, but I have other reasons I generally prefer to expose interfaces (distinct from classes, even if they’re identical in shape and even have the same internal purpose) at most API boundaries; avoiding that particular footgun just turns out to be a nice happy side effect of the preference.

Re: TypeScript: Branded Types

#90

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

flow is dead (for jobs). I'm gonna annihilate Dart (apart from jobs)
Post reply on HN