Hacker News new | past | comments | ask | show | jobs | submit login

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 argument that is expected to be an email address.

If you argue that this is not a common problem in practice, then I tend to agree. I haven't seen code with mixed up arguments very much, but when it does happen it can have bad consequences. IMO there is not a big cost to pay to have this extra safety. In TypeScript it's ugly, but in other languages that natively support this typing like Scala, Rust, Swift and Haskell, it works nicely.


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


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


Branding works on primitive types as well, which is I think the most interesting use case.

I would also agree that it's harder to confuse complex types as any single instance of a type is unlikely to overlap once you have a few fields.


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.


Or double your GC burden by using a wrapper class.


You've never had values that are only valid within a specific context and wanted to prevent using them incorrectly?


Yeah structural typing assumes that all fields with the same name & type mean the same thing, but that clearly isn't always the case.


I use branded types for database id’s to avoid mixing them up. It seems like better documentation and might catch some errors, but mixing up database id’s seems like an uncommon error, so hard to say.


You don't use them but your favorite libraries do.


This is very accurate, in particular when the output type of something depends on the input type.

Data validation, typed database access, or functional programming libraries are good examples. Particularly the modern, leading libraries of such areas, if you look into their code you'll generally see very intricate typing. For FP libraries it's particularly tough. I like to use Remeda which emphasizes being very type-safe, but that means it's inherently more limited in what functions it can offer compare to other libraries which choose to compromise their type-safety. These kinds of techniques mean that libraries can offer greater functionality while remaining type-safe.


Not necessarily wrong. You’re probably doing the same work at runtime. That might be either just as good (a subjective preference) or more correct (for certain kinds of dynamism) depending on context. In some cases, there are clear static invariants that could be guaranteed at compile time, and some folks will be more inclined to squeeze as much out of that as possible. In my experience, the benefits of that vary wildly from “total waste of time” to “solves a recurring and expensive problem for good”, with a lot in between.


Probably not.

I could definitely see using something like this to force some constraints on my favorite bugbear for my problem domain: values that should have units. It matters a lot if "time" is nanoseconds, microseconds, or seconds, but most time-related functions just take in "number" like that's okay and won't cause thousand- or million-fold magnitude errors.

This is one way to provide some language safeguarding against treating 5 nanoseconds as the same as 5 microseconds.


> The need for tricks like this (and other complicated types) is simply not there.

It just depends on how constrained by types you want your code to be and how much time and effort you're willing to spend maintaining and writing code that fits within those constraints.

Sometimes complicated types are introduced because you want to maintain editor features such as find by reference and the ability to refactor later. When it works, removing or adding new features feels fast, easy and safe.

In the articles case you could differentiate between an email string type and a user id string type. Maybe sometime in the future you want to change the id to an integer instead, so now that it's already distinguished you could find all those places where that's applied.

That's at least a selling point, in practice I've used this a few times but it doesn't come up that often. Sometimes the blast radius of a type isn't big so it's not worth doing.


Nope, if you have lower expectations for your type system and are using TS then you are properly where you want to be.


Yeah no, IMO this seems totally extraneous and a layer of complexity not worth introducing to any project. I've never encountered a case where I care that I need to differentiate between two types of exactly the same structure, and my gut feeling (without actually prototyping out an example) is that if this actually makes a difference in your code, you should probably be making the differentiation further up in the flow of information...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: