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

If I understand this properly, I’ve needed this in typescript a lot.

I can make `type uuid = string` for self documentation, but a lot of plugins will just label it “string” and developers can (and have) mistakenly put some other identifier, like the robot’s hostname.

Of course we validate at the API but it’d be more skookum if we could prevent accidental wiring together of front-end components that make this error.

String literals help a ton. Gosh they’re wonderful to care about the shape of a string in the type system. But sometimes I really want to say “strict uuid” as in “I don’t care if it quacks like a duck, it’s not called duck.”




As spockz pointed out, you’re looking for the new type pattern. Rust supports this explicitly but you’ve got to do some workarounds to get it in typescript.

https://kubyshkin.name/posts/newtype-in-typescript/

Unfortunately doesn’t help much when you’re dealing with functions from packages someone else has typed.


Check out what are often called tagged types.

You basically define a `type A = string &{a: SomeSymbol}`

And then have a type assertion function that just returns true, and you have control over the places A can come from


Yes, it's the standard way if you want implicit compat with the base type, so you can pass a value of type `A` to a function expecting a `string`. An other name for this pattern is "branded types".


I really like the Effect schema library for tagged types. I’m not sure how well it works for primitives, though.


Ah yeah I remember poking at that. I should review it again. I think at the time it felt like a bit of a hack, but maybe some features of the past years of TS updates have helped.

I assume the idea is to lie to the type system about the existence of the symbol, and at runtime it is just a string.


To me it feels like less of a hack if I can make the type declaration not a lie. e.g.

   type FooID = string & { typeName? : "FooID" }
Read as 'of course this thing doesn't have a typeName[1] property, since it's a string, but if it did have the property, the value would be "FooID"'. You can then cast between FooID and string, but not between FooID and some other type that declares a typeName property.

[1] I actually tend to use 'classRef' with an RDFish long name for the type, but that makes examples longer and isn't the point.


That’s a really helpful way of framing it. I’m tempted to try this with our uuid type and see how well it works.

I think I’ll also try to base it on a template string too if that’s possible. Given we use a standard dash segmented uuid string.


Haskell has had this with `newtype` for ages. Type safety without runtime overhead.




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

Search: