5 ms·
It took me so long to fully appreciate TypeScript's design decision for doing structural typing vs. nominal typing. In all scenarios, including the "issue" high
by msoad 2y ago
It took me so long to fully appreciate TypeScript's design decision for doing structural typing vs. nominal typing. In all scenarios, including the "issue" highlighted in this article there is no reason for wanting nominal typing.
In this case where the wrong order of parameters was the issue, you can solve it with [Template Literal Types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html https://www.typescriptlang.org/docs/handbook/2/template-lite...). See [1].
And for `hash.toUpperCase()`, it's a valid program. TypeScript is not designed to stop you from using string prototype methods on... strings!
It's more pronounced in object types that some library authors don't want you to pass an object that conforms to the required shape and insist on passing result of some function they provide. e.g. `mylib.foo(mylib.createFooOptions({...})`. None of that is necessary IMO
[1] https://www.typescriptlang.org/play/?#code/MYewdgzgLgBA5gUzAgTgQyggEmiALGAXhgAoBLMABwFcoAuGaFCuASgYAM9c8EATAPoASAN5MWAXw5EAfDBEAoGDBQIo1FGBhce-YSIo0oUmAHpTMAGIgUMPggC24JhjLgANDDSVKSPixgAIm58PUCYKBAYQ1oFCQBuBQVzGABJezQAG0yAT08AdwQYcFyYfLQwWEiYSlwIGBDeeuqoPDJ6gDNqMGAoNzAFUEhYUAda1Rx8IlJGzka9UXEwOClPGPpGKGZl9hgAIxAQTIQK2XklFTUNLS3qBESEpJSAUQAPNDHjmGoINERB5ywH6oVJUWjTQIQBDAVRQAAiGDQgUSQ2gDR400QyHQmEmeBIwJQoKMrESyQsAHUyK0QOC9ugwPY+BEcr4IJ5WqgEAByepgKIUfzAVzgCLcKq8S7qTT8TbbODRepodH4AB05JgcIQADcEJkQL4UPUHGgcjBUChwAhaRBSlsTrBqV4lZc4NRMmhbEsFXtoWhgWKubyYPzog4HPwyBgikNMK9YBgoGhgLxmdVqZ5jmh-MsIlEHO1gZ4EKq4OrUUcS-q4CRGqrIgBVHyoADCuAQJFYpKeFgAciA+sAipyap6PmpUErVNEtBRQChVL1iih7CgAcMYCaoCmEPViKNxtgeASoUSwVBPI1SUA https://www.typescriptlang.org/play/?#code/MYewdgzgLgBA5gUzA...
- skybrian 2y agoHow do you do this with template literal types? Does that mean you changed the string that gets passed at runtime? The nice thing about branding (or the "flavored" variant which is weaker but more convenient) is that it's just a type check and nothing changes at runtime.
- jacobsimon 2y agoThe demo they posted demonstrates how to do it. But I don’t think it’s a generally good solution to the problem, it feels like it solves this specific case where the type is a string hash. I think the evolution of this for other types and objects is more like what the OP article suggests. I wonder if a more natural solution would be to extend the String class and use that to wrap/guard things: class Hash extends String {} compareHash(hash: Hash, input: string) Here's an example: https://www.typescriptlang.org/play/?#code/MYGwhgzhAEASkAtoFMAeAXZA7AJjAyugE4CWWA5tAN4C+AUHcAPZYTrTnbJFibwRIAvNAAUZAA4BXdAC5obUhQCUc-kIB81OtGhFk6SUSzQsyAO5xEYrFPRKA3NAD0T6ADEmRaDmQBbFgq8JCwANNBg4uLYOGSUAEQIiMg4APpx0OhM0BLSdDT2DC7QAJI+YCAgAJ5hZsjQLFXQZmBY7JnQ4pAwiQLIMO3oCCQwAGaSWMDowViMAezMvp16atDCIj0IqohhObLyxLEq0ABGTEwgyC2rmlTauvqGxsSSyAX5ha4AoqhgixfQkggYE4s1Y7EB3GKNmkq2gcQgyGAenQABFeGA4gVmGDoBtYZxTDw+FYIUQobYHB9oAB1EiDJgw448XDJDKVKIQMKDbjIADkMCwWTIMWAQRYGUSbQQdWRj1ZCli2RgYFxiAAdHQiijkAA3ZAgJhRIgwXxgSooIhEFjIBkQRrES7sOnhZX3ciScBeBUUE6IsAQiU8-kmIW+XzJEi8OrYzAYcLodBgYDSnAZIXoMIXMAxH3tXzDCFhZBq8ga7EQc7Fg3kdbqzIAVUi3AAwpBkCIlJTNa4AHJMKbAOrcjpgHjhzDG8J6bLGMjMS2I9ieHxEUFsaCm9DJvqwhZLZBqESk8nSMIbBxAA https://www.typescriptlang.org/play/?#code/MYGwhgzhAEASkAtoF...
- mason55 2y agoAs mentioned elsewhere, what this is actually doing is showing that string and String are not structurally equivalent in TS. If you add another class Email that extends String, you can pass it as a Hash without any problems. And you can get rid of the Hash stuff altogether and do something like compareHash(userInput, new String(userInput)); and that fails just as well as the Hash example. Using extends like this doesn't actually fix the problem for real.
- stiiv 2y ago> A runtime bug is now a compile time bug. This isn't valuable to you? How do you get this without nominal typing, especially of primatives?
- dllthomas 2y ago> And for `hash.toUpperCase()`, it's a valid program. In a sense, but it's not the program we wanted to write, and types can be a useful way of moving that kind of information around within a program during development. > TypeScript is not designed to stop you from using string prototype methods on... strings! No, but it is designed to let me design my types to stop myself from accidentally using string prototype methods on data to which they don't actually apply, even when that data happens to be represented as... strings.
- lIIllIIllIIllII 2y agoThis 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 abstraction leaks when prodded. A hash isn't actually a special case of string. You shouldn't inherit from string. If you really need the branded type, in that you're inheriting from a base type that does more things than your child type.... you straight up should not inherit from that type, you've made the wrong abstraction. Wrap an instance of that type and write a new interface that actually makes sense. I also don't really get what this branded type adds beyond the typical way of doing it i.e. what it does under the hood, type Hash = string & { tag: "hash" }. There's now an additional generic involved (for funnier error messages I guess) and there are issues that make it less robust than how it sells itself. Mainly that a Branded<string, "hash"> inherits from a wider type than itself and can still be treated as a string, uppercased and zalgo texted at will, so there's no real type safety there beyond the type itself, which protects little against the kind of developer who would modify a string called "hash" in the first place.
- kaoD 2y ago> 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. Cannot be built except by using that branded type -- they're actually nominal, unlike your example where I can literally just add a `{ tag: 'hash' }` prop (or even worse, have it in a existing type and pass it by mistake) 3. Can have multiple brands without risk of overlap (this is also why your "wrap the type" comment missed the point, branded types are not meant to simulate inheritance) 4. Are compile-time only (your `tag` is also there at runtime) 5. Can be composed, like this: type Url = Tagged<string, 'URL'>; type SpecialCacheKey = Tagged<Url, 'SpecialCacheKey'>; See my other comment for more on what a complete branded type offers https://news.ycombinator.com/item?id=40368052 https://news.ycombinator.com/item?id=40368052 [0] https://github.com/sindresorhus/type-fest/blob/main/source/opaque.d.ts https://github.com/sindresorhus/type-fest/blob/main/source/o... [1] https://www.typescriptlang.org/play/?#code/C4TwDgpgBAEghgZwBZQLxQcATgSwHYDmUAZFAN5TBwEBcUAREokvVAL4CwAUNwMYD2eTFAAm-AMr8AthGBJ8RdAAomyOvGQBKNAD5KWAK4Ru3MZJlyFSgOQJpEDUmvaA9C6hJ+Adyi84eKABJKAgANwgAgCMDHAAbEUokaFBIKABCAH5MoA https://www.typescriptlang.org/play/?#code/C4TwDgpgBAEghgZwB...
- deleted 2y ago[deleted]
- lolinder 2y agoTemplate literal types solve ordering for a very specific type of parameter-order problems which happens to include the (explicitly identified as an example) terrible hash function that just prepends "hashed_". But what about when you have an actual hash function that can't be reasonably represented by a template literal type? What about when the strings are two IDs that are different semantically but identical in structure? What about wanting to distinguish feet from inches from meters? Don't get me wrong, I like structural typing, but there are all kinds of reasons to prefer nominal in certain cases. One reason why I like TypeScript is that you can use tricks like the one in TFA to switch back and forth between them as needed!
- kaoD 2y ago> In all scenarios [...] there is no reason for wanting nominal typing. Hard disagree. It's very useful to e.g. make a `PasswordResetToken` be different from a `CsrfToken`. Prepending a template literal changes the underlying value and you can no longer do stuff like `Buffer.from(token, 'base64')`. It's just a poor-man's version of branding with all the disadvantages and none of the advantages. You can still `hash.toUpperCase()` a branded type. It just stops being branded (as it should) just like `toUpperCase` with `hashed_` prepended would stop working... except `toLowerCase()` would completely pass your template literal check while messing with the uppercase characters in the token (thus it should no longer be a token, i.e. your program is now wrong). Additionally branded types can have multiple brands[0] that will work as you expect. So a user id from your DB can be a `UserId`, a `ModeratorId`, an `AdminId` and a plain string (when actually sending it to a raw DB method) as needed. Try doing this (playground in [1]) with template literals: type UserId = Tagged<string, 'UserId'> type ModeratorId = Tagged<UserId, 'ModeratorId'> // notice we composed with UserId here type AdminId = Tagged<UserId, 'AdminId'> // and here const banUser = (banned: UserId, banner: AdminId) => { console.log(`${banner} just banned ${banned.toUpperCase()}`) } const notifyUser = (banned: UserId, notifier: ModeratorId) => { console.log(`${notifier} just notified ${banned.toUpperCase()}`) // notice toUpperCase here } const banUserAndNotify = (banned: UserId, banner: ModeratorId & AdminId) => { banUser(banned, banner) notifyUser(banned, banner) } const getUserId = () => `${Math.random().toString(16)}` as UserId const getModeratorId = () => // moderators are also users! // but we didn't need to tell it explicitly here with `as UserId & ModeratorId` (we could have though) `${Math.random().toString(16)}` as ModeratorId const getAdminId = () => // just like admins are also users `${Math.random().toString(16)}` as AdminId const getModeratorAndAdminId = () => // this is user is BOTH moderator AND admin (and a regular user, of course) // note here we did use the `&` type intersection `${Math.random().toString(16)}` as ModeratorId & AdminId banUser(getUserId(), getAdminId()) banUserAndNotify(getUserId(), getAdminId()) // this fails banUserAndNotify(getUserId(), getModeratorId()) // this fails too banUserAndNotify(getUserId(), getModeratorAndAdminId()) // but this works banUser(getAdminId(), getAdminId()) // you can even ban admins, because they're also users console.log(getAdminId().toUpperCase()) // this also works getAdminId().toUpperCase() satisfies string // because of this banUser(getUserId(), getAdminId().toUpperCase()) // but this fails (as it should) getAdminId().toUpperCase() satisfies AdminId // because this also fails You can also do stuff like: const superBan = <T extends UserId>(banned: Exclude<T, AdminId>, banner: AdminId) => { console.log(`${banner} just super-banned ${banned.toUpperCase()}`) } superBan(getUserId(), getAdminId()) // this works superBan(getModeratorId(), getAdminId()) // this works too superBan(getAdminId(), getAdminId()) // you cannot super-ban admins, even though they're also users! [0] https://github.com/sindresorhus/type-fest/blob/main/source/opaque.d.ts https://github.com/sindresorhus/type-fest/blob/main/source/o... [1] https://www.typescriptlang.org/play/?#code/CYUwxgNghgTiAEYD2A7AzgF3hqBzAXPAK4oCWAjkQmgJ4C2ARkhANwCwAUJyAB4AOSGFgw0+CACp4AwqhykUIGAB5xSANYgUAPngBeeAG9OASDhRgqCDXgBtHLgC6hVRpTsOAX3ecRY+JNwVdU14XgxNYDR4AAUYJDEhGgBpEBoAGn88AFkQHGAoHB19AJkUOQVlAxsk+Hl-YJQnTNwcvIKoDy1vDgB6ACo+zgBBDBwwAAt4KHgAInsZ7CQplCmYBlIMGFhrXxAAOn9x0iioCAgkAHcomiQiRcQzcPhgY4x5MGFREDQMjHGC1aaADkWCgaDQpFwKCgDAgCAwS1QCCgKCQf0UGQAZoJ4DASG86AgAG6nKhRP4Ai63CDAeBo8aKC7HBD-InwhnwNBQQnYL4HAAUAGUQAheNy+HC0HsAJScTjiDm4TSKUhgXl+Piwbm5RRRMAo+AMZEoGh-eS4PZyjgAOTR7IBAAMAPKayggB21KJoJCEi7-LBITFTeB0Y6owkweAMuAZOoUjAZEgQUgaWzenmncIwaFvNm1OgSkCEsoFUioNAOfnjUZ8ND4Ho9XAbcZEBh7ZB0HqhsBxb2YjA9cRfQU90h8AfHNBknoAFgAHABOACsAGJJ1QO8WMABaGcAJiXAEYAMwHud76W-DmCSHyU6Jku4JW03ae+Co0EfIinKxR0jAUAUAFABRPYLSxEgPjLdBsH9KYwDAEBx1ghB8TwZ91QQfUVkwUgzkNLCfxAWkmT+FDZniKA3QWNkYAhVB4G3bdDSILAP3gIlVRAbdaK5GUrSGCBvXfO1YIBM0olIAs4S3UsGOOeBk1DcIXyWaYIRQXA4WwPADgASSDG47guFFhDUs5LhDIgIDeQsdNwH5iDQBAHQCZ8PXkTAQHMS0uA4GwACVvNpOhBGRJhWPsjDdilStqwwWt6y7YjSCIOh2x9HoAAEUk4lAACEAHE4GVHopxgTi8twbd0Rqr40FHcdt3AJBaC8uhtwYLYUBeTTtxRYA6rEGr0PNbcADYwExcaQCPcalz3Y9gFlPyFUUFkoDzI0Qmc2jTmeY4wCIcFoJOCLQQA81OSkvDYHgTFvIwIg4HJJYhzEEcYDHDADgAVRQbEhBIAoQCsDIPyOTSozBeB+RoXJpRDJA2VpQGTJgYB8BMZibCyVVe0DAd3pAT7vpXPcAAY9zims6wbJs-lbDLO27An+0HYdGoncFp0pi9sdsPGe1awmOY+rmV0PJcZwpucaYSunG2bJmOx6IW2aJzmvvHHp12+HopZlucVuMHH1ZF9nidJ8dJaXOcKYAdnlxL6eVttVfNvtNfF7XuanfWpfth2Vs4LKxWkkBOAdaPOCkgQhEwwwXSoqgPHuuI6HgEEvm3B7MCBbpXyGBDbjKa00qNSN9GTt0lBQCuMSz4vkHxcvGEUIEuh8L54Gb0uMHy04UUQvR4Brqg64bmAMiBPv8UH6AUEQzvugbQ4XJcTQPU1LZCSzKYLKuciOz4PDFHuF5MQeuAylIEGCIwC4RRWGKMj9daYZmEg1FRC4UAWJk+EPwHAAGI4nDoWDIyB0D-nPuie6zBzhMihpgPEX4XpYw4K+BU5onQKFHuPEAShUHmi7lgnuODNLiCpAQ10E8SGaTIZwNeqhj4+lPnCaeKFnKYROHAA+SDiL3CNIgMEZlQhQAmHSdEkYYbomsKydk1BtTEB6ooKwV1dgHDWtYWACAmBkQdAw3A8AABkhh4AAH1LGUTdNYwg39f4rA8A6XyLC1JEiQP+WCCkjSsjLM9DIRl4BUmsrSTy4RzDwE1OCciMxN7-2iVqPejdkypm9L5V81oQAXEobgPBCBq50KIcYmeeSCkr27n4bJuTIa4GoUsIpKcSmbHNGUupDTKl+TXraC4KEaBAn4SfOE4RfzOR3vfV+nIljwOxIfK6gCID3SgHhe4J8z6+UxJBN4DEeyPRADU8p+D+TSkIIcupBTDAmDXkMa4txRE4RFFAsRV1MQZ2DMYt8uF8I-kuMRA4AAJP5tEgn3LoFAaw7F9SYHgOc3B+CESwpyXkhpGQBocS4hxXUUBfKmFyM9FYQIFB9LNFDJEQIphRDhZpAp7gPBWhYUcKIizlmrMResuEaLyQMl0fwrZPVtQljONYS+19NCfDEFKTg0CYWkvqTQ-QeyQbUvyQoE5lKkW1PNA01ePR4AAvWkCE4ENz4QO0oGOkxT1Tmk2ds6CDx9lzzLlPE5hAnUYDbpXK5HA8VPWzPAPcGr3WesUHSq0-KoIMSVBgLISIaBgJgO6-kkiW7OvbjAN1JdW5T1Ob3LNZQF7DwQEYH1cA-UrBnEG-NA8h5LxAGG7peqcHMrwksjl1AiAIW+GgLZwrfLRtjQoeNggk1KvCMGl10ppS6vgPlSKEkQmoBBBkI0+pjoIA2NDV6BFQj8GTGADYv4YnOVpDDA0DoJ3po9FozgA640JqTReGdABNe52EnIIFsVQDipJviKRTEo3lwIAxWoREgXyMrPypo9VPUeY6QCXsricmdzaQmtsQOws+nJO2IXBL2qwEHyxsRyYh8++gU39xDZGAA1AGmdtyPmwJEuEUFdx33Jp6pycY1JloftxOAZ6EI8wxQQZGIykYv3wnqvccFqZ0SZ1QagJUsjOPcbEPh6w3kIRwJmXyKpCBoi6gYvoEtxh-yEEIZPdNGRDN0VQGQ4w0JCSEGMXS9w0cHRWiyvqcIuBBDWGJqHUAfA4A+aEb9HhBgsrJhQGoZoz404ROCpwPoPRuD8EEBKhAlniYZASaPRxlxtCj2JmY5opRyiKCCK4JhvQBicCCrRUEKxJN0kjPYaKPcGCSLi4ijYUQbxNmhEs18DBrBwFClVciZgLAoF-A6OweAHCuKtAAdXGNYBSC6FC4a5DAGgAB+K0h4-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-R-iK2lAOAdCSe9BQCdExBUC0A9G9AgDZG5WOA703WyZenFSiHydFMKd8ZKdODKdSetGWBfH9FjCDGeeADfh406KQHueRB-XWM-XmIoUqbWkTnEDfAZCLzjHvNMfcfBE8bvm8ebHIgdCBGmaBGvW-NWhSNgBxu0nDN4KiFCjhKgpsG4xJSNLcemC-zUFDLOdmJJyNoaZAF7WMvRNSnSlVjDgpBQBeh6bUB6HMBJDrUGjJN9hGg7MZLQG3DjjiBRm3CBMZO3DhDZAgG3HVe3CXEdj3ExDAEPAPCjcxABepzcrhFLq8sZwTgrvuICr5xrpCsLh7mBdQDBYhd3uAHibaSzhFbyzythxwjlLmJoAhf5A2GcERhLXrMOumb2GMXrabeIo1QLdBfBfEDIXpT8gWqedgGchUGx04PgAHaLYEO0C0F7Zbe9XbaLJWE7eed7cRhhm9IwF9K5agBUGSdxa6VHeN2r3gB4FHm7cxBoH5AMCjFBnOEICBC-xpApQ8GnUvZhRJdHm3Z4GnUQPXifNuJJY8iiCfbZdffRaulHe4dDl4f8zsNp3oOEYy3TZ7n3cPbaBwBUDMIsNkb0YCEMbqsXdd0kekfMNnZkMH0UaUJKy+GcLEBfscBsFI+1AcCCMGA4Ea0HyP0jtGx63uHtMGzvBG17v40mxf3wnXKjI4HW02zhaiB227W2COz8hOxALO1uMoJRuRAGzuwe3gCexew4DezkZmO+3EF+wpn+wr0EEIBmFBwJTA5EaECEXHZWAIpxEFJRxUQxyapnezH2lCnx20iJwYEilJzRB3Qpyp3QJLrpzQO8qw78D8vuLZxQA51T0ruC6Ctrvzjzb8GbxLaUCl1lzbxVzVw1y1x1z1ybjFy+Cvsg1N0bwt2a7EGtxajtwoOiub0o7asUA91MxH0V390D2Dzq7D0KgjyjxjxnQTz+ARP647IzwQXmShlz27XtULwJZLwBL8Ec5gDTprzjXrzjSby65byq99w7y7x7yc7kYH0SGHzu8VzHwOdU7i6n0wBn1CPn0waKaXzTJX1RDX0oM3yWDW4whZQP10+P0rz-wvzKCANHlh8gIf0Ls8qQ8wJQ58PQ486yxAJy850oP0efHYJo8sL0bo8SHkLRRNCEL0AayLCAcEggB0Mp+IgMJK4QA0I5+0IhOLeKBndp6kPp7kKUcUI8LUNM2MA46ZQCGOdM9SAtXEBY5ADY4cCaBZZkckPSpsPPgkebQCDyy1516V+OACHcJUNUPUPZ7ZE5+54MPt8IE0cUBMA8BsCew18t-mKWwcBMGcG6CtAZWCPgD6F7gXoXvgCkCdAABFgJe58onQZcU+9Ju8E-og9JgJE-4AQEAonQsgoxaYkotgLgwI3Z10YAzTxUsSNJgAXpBAWxVKc266egKZMQHZTxjwHZDxppHYHoZwZwQAFxjwZxgBDwHY5wQAGAFwHZzAB-wAwB8g5xe+ypbg6+QAehJM9hxqMBzOo-4BOAT-+R1t3m3x8pxBVt4+pAKYy+FYK+oAq+WLWxa-6+yhG-5Bm-vhW-joPQDvpgC7498++A-Ifg7BH5j8J+U-GfnPwX5L9gAK-BCOv0377pNAzkbcAhApgrQo+QjM-kwWcgwA9ItIGhulToZAhiCJA4AJ3EIGEDXwsaZsgUEECkDyu1A0gTPCYGKAWBNAzuPWQEGCChBwgkDh+ExRPwMMjOE9GhjIgcDaQ0YWnPWQYE9whgwAUMCgDYHkDp6cg-XGoPkCkD+BIgowcYMEFrx0UCg+gRwHrJtdusKAagaPH5C2CFAmMHTooE4GGgUQFQN1HoI0G8ZdAOgNtgIJlTMB9g5wXAPyAdAAASAwE4MUBpxbsUpDwSgGcHwBohsQw-kgF+jN1FAm9ZyCchcR4D4AF7Kwc0RhRiD729g-QI4M8HERCAOg5jHMVICKBCA3ArYAiBoGIx-Ba7IIeWBCF7AwhEQ6IeUKaEwB4hiQ4YUIjSE1CMhWQhILkJAD5CHQiMesmvDEEjwEQswnIWCBZDrRCBxQ6wSblsHUChgPUW0G8HvYOD0hdQ4ge4NiHA5WhvAtgeYlUHqDSBnQgIYQPrJHDiB1Q5IcRBXQ1CYAhQ+suUJoDUDfhzggEX8KBF7CrQBwq9tGjkEOD3hnw24tEKyAFBxgewbqBYDoAnI9gCIQUHKX5AclpQLiDVHILhGlCYGz1SljwPaGaDYYKIkoSsL1Thd6RggPhMiB5ofo6I6NFkSB2i5YAJBLwYAEcwUCTNRkSyTdFDQPQYBfwCgmQZMAdAwwkR5iB4QyKHqvMsI5Lf8ityIBgNgRaIgwBiL+DYiBoPofEYSOJGkjyRMMDUawOABUi2u0aF4foLIFMi9AhhUwXqgSEwpha0FdQVyJNK81iBx-AQVEJNGYjzRPUS0QC2tGFMSR40MkbTV7g+DSBlg+ETCjvTMD2hJw4AG6N8HIivRqIteJ-SiC19r+TocQACjxwcjIwQwa0AX3MDqDYYdjfjBbXgq8jImQYFuHRBABGjVhokRUSKO8R80OQDoUxAZ2OZZhnIkaFAKiMjGmisROIuMQSKQBEjExto1MQ6JoFlZCxGYkoYQO+GKB+QiIm4cABOQZBXR6Yy8VOmPEohjhpwtED23PFuC7x143IAeLvHLCTBIHBdJiBWRCQHxdg4gfmLOGvjcgcgq8fABzH1jSBJyX8cILLEpFAJeEV6EgBAlPjgAEE+9meKgkXiYJcEtoSOh6jfjEJgo+dADWvzhiTxJVG8a8I-GwSvxt4iiX+OMFrxgk76azkkMDGeQV04AXWl7R5SDJuRwkWvuZ0IHBC4Q-QpAOEIYnuirRmQ7ITAHmFsT2JAglCSNR5GatwxCk3wUpM2GqTth6qLkG8B7RNCvQcpDSWvFXRCS6WPiSSSULon4SMA0E9UvpIQnxjlJcwkyVOiMG2SqJCkNCUJDbGSQ+iAxGkIUM8l3j1xRktSYjDMnHBMQlktMYxPYm2TBJ447ScJBClOS14N9d9HfSWAWBRSRAK+P+g0CYIpJJuKcAkEHgrB9A07A3q4JoHLsrh8AYCDwEgBEBQAJ7NKe6Mo53DvBjE94d0KzF9CBhkYu4WMJhR1TFAnUaYakJiHTC4pKkhKQUNhF+R6y80mAA1NcnuTPxGAcif5I0kiCtJzKQQFq0IG7T9pxEx4UxJinqSbJeqBdLpJ-w3SiA9UlEK5JOlHSTpSEs6ayPgBcTPBcXXaYtJWAtj+JoQL7LBFuBgN+kokkMbyLQCo0gAA https://www.typescriptlang.org/play/?#code/CYUwxgNghgTiAEYD2...
- mattstir 2y ago> 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 argue that doing uppercasing a hash is okay because the hash happens to be represented as a string internally, and strings happen to have such methods on them. Yes, it's technically a valid program, but it's absolutely not correct to manipulate hashes like that. It's even just odd to point out that Typescript includes string manipulation methods on strings. The whole point of branding like this is to treat the branded type as distinct from the primitive type, exactly to avoid this correctness issue.
- treflop 2y agoThe real problem is that hashes as strings is wrong. Hashes are typically numbers. Do you store people's ages as hex strings?
- mattstir 2y ago> 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.
- vjerancrnjak 2y agoOne tricky scenario I stumbled on is `.toString()`. Everything has a `.toString()` but some objects A have `.toString(arg1, arg2, arg3)`. But replacing A with something that does not have toString with arguments still type checks, yet will probably result in serious error.
- Quothling 2y agoThis is sort of by design. Generally speaking Object.prototype.toString() does not accept parameters, I think the only "standard" implementation which takes parameters is Number.prototype.toString(). You can overwrite .toString with your customized functions, but doing so is full of risks which can basiclaly be summed up to performance overhead and unexpected behaviour due to how you're not really in control of an object's state... As you sort of point out here. I know it's very tempting for people coming from OOP languages to use their own custom toString functions, but you really shouldn't. If you really need a string version of an object for debugging purposes you should instead get it through JSON.stringify. This is partly because .toString() isn't really meant to be used by you in JS. You can, and in a few cases it may make sense, but it's usually unnecessary because JS will do it automatically if you simply wrap your non-string primitives in the string where you want to use them. In general it's better to work with objects directly and not think of them as "classes". I think (and this is my opinion which people are going to disagree with) in general you're far better off by very rarely using classes at all in TS. There are obviously edge cases where you're going to need classes, but for 95% of your code they are going to be very unnecessary and often make it much harder for developers who may not primarily work with JS or another weakly typed language. Part of this is because classes aren't actually classes, but mainly it's because you can almost always achieve what you want with an interface or even a Type in a manner that is usually more efficient, more maintainable and easier to test because of it's decoupled nature. I have this opinion after working with JS in both the back-end and front-end for over a decade and seeing how horrible things can go wrong because we all write shitty code on a thursday afternoon, and because JS often won't work like many people from C#, Java or similar backgrounds might expect.
- vjerancrnjak 2y ago