4 ms·
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 lab
by Waterluvian 2y ago
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.”
- mcfedr 2y agoCheck 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
- Waterluvian 2y agoAh 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.
- TOGoS 2y agoTo 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.
- Waterluvian 2y agoThat’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.
- demurgos 2y agoYes, 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".
- steve_adams_86 2y agoI really like the Effect schema library for tagged types. I’m not sure how well it works for primitives, though.
- spockz 2y agoHaskell has had this with `newtype` for ages. Type safety without runtime overhead.
- feznyng 2y agoAs 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/ https://kubyshkin.name/posts/newtype-in-typescript/ Unfortunately doesn’t help much when you’re dealing with functions from packages someone else has typed.