19 ms·
Branded types for TypeScript
- foooorsyth 2y agoReally ugly way to avoid something already solved by named parameters and/or namespacing
- demurgos 2y agoThis is more about nominal VS structural typing. I don't see how named parameters or namespacing would prevent accidental structural type matches.
- culi 2y agoTypeScript is a structurally typed language on purpose. But even then nominal features already exist that would have solved this problem much more elegantly. Such as `unique symbol` https://www.typescriptlang.org/docs/handbook/symbols.html#unique-symbol https://www.typescriptlang.org/docs/handbook/symbols.html#un...
- mattstir 2y agoDid you... read the article?
- beeboobaa3 2y agoYou might need to read the article again, because those things are unrelated. Or you should explain what you mean.
- foooorsyth 2y agoI read the article, thanks. It makes the claims that (1) a string of a specific form (a hash) could be misused (eg. someone might call toUpper on it) or (2) passed in incorrect order to a function that takes multiple strings. Named parameters / outward-facing labels (Swift) completely solves (2). For (1), the solution is just ugly. Just use the type system in a normal manner and make a safe class “Hash” that doesn’t allow misuse. And/or use namespacing. And/or use extensions on String. And/or add a compiler extension that looks like outward-facing labels but for return types. So many cleaner options than this nasty brand (yes, that’s the point of the article, but the solution is still hideous. Make it elegant).
- aidos 2y agoRespectfully disagree on the elegance. This looks pretty neat to me: type Hash = Branded<string, "Hash">;
- beeboobaa3 2y agoThe point of branded types is, among other things, that you do not need to introduce a wrapper class which consumes additional memory.
- throw156754228 2y agoReally? I'm surprised you mention memory is even a consideration, never even heard it raised as far as typing choices are concerned.
- beeboobaa3 2y ago(More than) doubling the memory required for all of your integers would be silly. You could use `{userId: 12345}` everywhere, or you can use a branded type and it's just `12345` at runtime.
- golergka 2y agoAre you worried about compiler memory consumption? Because it's not a class, it's a type, and it's erased at compile time.
- beeboobaa3 2y agoRuntime. Not compiler.
- golergka 2y agoNone of this exists at runtime.
- 2y ago
- bradrn 2y agoIn most languages, doing what this article describes is quite straightforward: you would just define a new type (/ struct / class) called ‘Hash’, which functions can take or return. The language automatically treats this as a completely new type. This is called ‘nominal typing’: type equality is based on the name of the type. The complication with TypeScript is that it doesn’t have nominal typing. Instead, it has ‘structural typing’: type equality is based on what the type contains. So you could define a new type ‘Hash’ as a string, but ‘Hash’ would just be a synonym — it’s still considered interchangeable with strings. This technique of ‘branded types’ is simply a way to simulate nominal typing in a structural context.
- beeboobaa3 2y ago> In most languages, doing what this article describes is quite straightforward Well, no. In most languages you wind up making a typed wrapper object/class that holds the primitive. This works fine, you can just do that in TypeScript too. The point of branded types is that you're not introducing a wrapper class and there is no trace of this brand at runtime.
- JonChesterfield 2y agoThere's never any trace of typescript types at runtime.
- mistercow 2y agoIf you wrapped the value to give it a “brand”, the wrapper would still exist at runtime. The technique in the article avoids that.
- aidos 2y agoBy “trace” I think GP meant that the required wrapper is still there at runtime but was only in service of the type system.
- mattstir 2y agoI think what they meant is that at runtime, you don't end up with objects that look something like: { "brand": "Hash", "value": "..." } which would be the case if you used the more obvious wrapper route. Using this branding approach, the branded values are exactly the same at runtime as they would be if they weren't branded.
- sleazy_b 2y agoI believe type-fest supports this, previously as “Opaque” types: https://github.com/sindresorhus/type-fest/blob/main/source/opaque.d.ts https://github.com/sindresorhus/type-fest/blob/main/source/o...
- jakubmazanec 2y agoTrue, although "Opaque" was deprecated and replaced with similar type "Tagged" (that supports multiple tags and metadata).
- JonChesterfield 2y agoGiving types names is sometimes useful, yes. To the extent that languages often have that as the only thing and maybe have somewhat second class anonymous types without names. It's called "nominal typing", because the types have names. I don't know why it's called "branded" instead here. There's probably a reason for the [syntax choice]. Old idea but a good one.
- LelouBil 2y agoIt's called branded because the article is talking about branded types a way to enable nominal typing inside the structural typing system of Typescript. Saying "nominal type" wouldn't mean anything.
- chromakode 2y agoAlternatively for the case of id strings with known prefixes, a unique feature of TypeScript is you can use template string literal types: https://www.kravchyk.com/adding-type-safety-to-object-ids-typescript/ https://www.kravchyk.com/adding-type-safety-to-object-ids-ty...
- comagoosie 2y agoIsn't there a risk with this approach that you may receive input with a repeated prefix when there's a variable of type `string` and the prefix is prepended to satisfy the type checker without checking if the prefix already exists?
- lolinder 2y agoNote that the prefix was never intended to be looked at as the real problem. That's not a hash function, that's an example hash function because TFA couldn't be bothered to implement a proper one. They're not actually trying to solve the prefix problem.
- kaoD 2y agoThis is why I always use `Math.random().toString(16)` for my examples :D People often get lost on the details, but they see `Math.random()` and they instantly get it's... well, just a random thing.
- freeney 2y agoFlow has actual support for this with opaque types. You just use the opaque keyword in front of a type alias ˋopaque type Hash = string` and then that type can only be constructed in the same file where it is defined. Typescript could introduce a similar feature
- msoad 2y agoIt 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...
- mpawelski 2y agoI like the brevity of this blog post, but it's work noting that this mostly feels like a workarounds for Typescript not supporting any form of nominal typing or "opaque type" like in Flow.
- herpdyderp 2y agorelated recent discussion: https://news.ycombinator.com/item?id=40146751 https://news.ycombinator.com/item?id=40146751 (about https://prosopo.io/articles/typescript-branding/ https://prosopo.io/articles/typescript-branding/)
- OptionOfT 2y agoInteresting way of elevating bug issue to compile time. I'll definitely try to apply it to my TypeScript Front-End. I use the newtype pattern a lot in Rust. I try to avoid passing strings around. Extracting information over and over is cumbersome. Ensuring behavior is the same is big-ridden. An example: is a string in the email address format? Parse it to an Email struct which is just a wrapper over String. On top of that we then assign behavior to the type, like case insensitive equality. Our business requires foo@BAR.com to be the same as FoO@bAr.com. This way we avoid the developer having to remember to do the checks manually every time. It's just baked into the equality of the type. But in Rust using type Email = String; just creates an alias. You really have to do something like struct Email(String) Also, I know the only way to test an email address is to send an email and see if the user clicks a link. I reply should introduce a trait and a ValidatedEmail and a NotValidatedEmail.
- kriiuuu 2y agoScala 3 has opaque types for this. And libraries like iron build on top of it so you can have a very clean way of enforcing such things at compiletime.
- comagoosie 2y agoOne nuance missing from the article is that since branded / tagged types extend from the base type, callers can still see and use string methods, which may not be what you want. Equality can be problematic too. Imagine an Extension type, one could compare it with ".mp4" or "mp4", which one is correct? Opaque types (that extend from `unknown` instead of T) work around these problems by forcing users through selector functions.
- kookamamie 2y agoThese are sometimes called "strong" or phantom types in other languages, e.g.: https://github.com/mapbox/cpp/blob/master/docs/strong_types.md https://github.com/mapbox/cpp/blob/master/docs/strong_types....
- jweir 2y agoElm makes great use of these, for example in the Units package: https://package.elm-lang.org/packages/ianmackenzie/elm-units/latest/ https://package.elm-lang.org/packages/ianmackenzie/elm-units... Very nice to prevent conversions between incompatible units, but without the over head of lots of type variants. https://thoughtbot.com/blog/modeling-currency-in-elm-using-phantom-types https://thoughtbot.com/blog/modeling-currency-in-elm-using-p...
- stiiv 2y agoAs someone who values a tight domain model (a la DDD) and primarily writes TypeScript, I've considered introducing branded types many times, and always decline. Instead, we just opt for "aliases," especially of primatives (`type NonEmptyString = string`), and live with the consequences. The main consequence is that we need an extra level of vigilance and discipline in PR reviews, or else implicit trust in one another. With a small team, this isn't difficult to maintain, even if it means that typing isn't 100% perfect in our codebase. I've seen two implementations of branded types. One of them exploits a quirk with `never` and seems like a dirty hack that might no longer work in a future TS release. The other implementation is detailed in this article, and requires the addition of unique field value to objects. In my opinion, this pollutes your model in the same way that a TS tagged union does, and it's not worth the trade-off. When TypeScript natively supports discriminated unions and (optional!) nominal typing, I will be overjoyed.
- anamexis 2y agoCan you say more about natively supporting discriminated unions? You can already do this: type MyUnion = { type: "foo"; foo: string } | { type: "bar"; bar: string }; And this will compile: (u: MyUnion) => { switch (u.type) { case "foo": return u.foo; case "bar": return u.bar; } }; Whereas this wont: (u: MyUnion) => { switch (u.type) { case "foo": return u.bar; case "bar": return u.foo; } };
- stiiv 2y agoSure! You need a `type` field (or something like it) in TS. You don't need that in a language like F# -- the discrimation occurs strictly in virtue of your union definition. That's what I meant by "native support."
- anamexis 2y agoIsn’t it the same in TypeScript? You don’t need an explicit type field.
- KolmogorovComp 2y agoIs it erased at runtime? The article doesn’t mention this.
- lolinder 2y agoYes. In TypeScript, everything placed in type signatures or type definitions is erased at runtime.
- mattstir 2y agoIt is "erased" in this example in the sense that the hashes are just strings at runtime, and not other objects instead. That's because the `generateHash` function in the article's example uses `as Hash` to tell the compiler "yes, I know I'm only returning a string here, but trust me, this is actually a `string & { [__brand]: 'Hash' }`". That `as Hash` is known as a type assertion in Typescript and is normally used when the developer knows something info about the code that Typescript can't.
- hombre_fatal 2y agoWhat's nice here is that `makeHash: Hash = (x: string) => x as Hash` checks that `x` actually overlaps with the branded type (string, in this case). At first it looks like you could just lie and say `x as User` or something but it's not the case.
- 1sttimecaller 2y agoI ran the branded type example listed in the blog through bun and it ran without issuing a warning or error for the "This won't compile!" code. Is there any way to get bun to be strict for TypeScript errors? Does deno catch this TS bug?
- iainmerrick 2y agoNo, neither Deno nor Bun does any type-checking. They both just convert the code to JS. To do type-checking you need to run TSC.
- rough-sea 2y agodeno does type checking. https://docs.deno.com/runtime/manual/tools/check https://docs.deno.com/runtime/manual/tools/check
- iainmerrick 2y agoOh! I stand corrected. That's a wrapper for TSC, though, right? Edit to add: I see, Deno embeds TSC as a library and tries to straighten out the mess of tsconfig. That must be a leaky abstraction, which makes me wary, but maybe it works well in practice.
- rough-sea 2y ago"embeds TSC as a library" understates the integration: https://github.com/denoland/deno/blob/75efc74931c1021fdc41c96f45a4e20ae4609e50/cli/tsc/mod.rs https://github.com/denoland/deno/blob/75efc74931c1021fdc41c9... https://github.com/denoland/deno/blob/75efc74931c1021fdc41c96f45a4e20ae4609e50/cli/lsp/tsc.rs https://github.com/denoland/deno/blob/75efc74931c1021fdc41c9... Deno makes certain tradeoffs that make it feel seamless
- iainmerrick 2y agoI'm now intrigued and might try it out. I like TypeScript but dislike TSC.
- c-hendricks 2y agoStrings might not be the best way of demonstrating nominal typing, since that's already something TypeScript can manage: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAEghgZwBZQLxQAZMUg+gEgG8FgAnASwDsBzAXwwCgGAzAV0oGNhyB7SqahEoRScYBHjIAFFTCtgALigkKNAJRRCDKDqgc+JKNmRLJKdFhwFCs+fSa6opCMFal+xpA1pN9lQ-oAtmBwzmZoUFKepjgANFC2ispkVNRqSgBGPDwANhBw-KgAfJraus6u7lBkrBAA3N4NDH6GrAgiAJKUcsARAETtHBUAImJwfQ0tvZ4RgsKi4mZSbZ3d8mp1UAD0W0Y4CQhQPMzV4NBmTDtQACpI5IcA7nwA5L1BYOR5AIQMVwCCpGorECQl6x1OkCgzxUqWeByglB4vUQCHI1EocAyeWqPCgIVEIPEpCOJ1AkOeZmeADpfltrmcoTCaHD7gikVAUWiMVjoMBcWToM9CE58gATPg5EBQADauFwGVElFFAF0lH0zBMoLRqVIAEwAZgALABWNTNAy9XCBMQcJAQQ7od6hCQ4ZbtUhdHrxTwbS67W73b08B4QABuIni70+9p+Uyg1uAtvtESdYVdnniKw9a2AvqAA https://www.typescriptlang.org/play/?#code/C4TwDgpgBAEghgZwB... Also, since all examples of branded / nominal types in TypeScript use `as` (I assume to get around the fact that the object you're returning isn't actually of the shape you're saying it is...), you should read up on the pitfalls of it: https://timdeschryver.dev/blog/stop-misusing-typescript-type-assertions https://timdeschryver.dev/blog/stop-misusing-typescript-type... https://www.reddit.com/r/typescript/comments/z8f7mf/are_there_common_bad_usages_of_as/#:~:text=The%20only%20situation%20where%20you,need%20a%20type%20guard%20instead https://www.reddit.com/r/typescript/comments/z8f7mf/are_ther... https://web.archive.org/web/20230529162209/https://www.bytelimes.com/why-you-should-avoid-type-assertions-in-typescript/ https://web.archive.org/web/20230529162209/https://www.bytel...
- deleted 2y ago[deleted]
- mattstir 2y agoI'm legitimately confused about why so many people in this thread are showing off template literal types as if actual hash functions just prepend "hash_" onto strings and call it a day. There are a lot of different types of data that don't have a predictable shape that TLTs just don't help with at all. While the pitfalls of mindlessly slapping `as XYZ` on lines to make it compile certainly exist (when the type definition changes without the areas with `as` being updated, etc), I don't know if branded values are really the place where they pop up. You brand a primitive when you want to differentiate it from other primitives that otherwise have the same underlying type. In that scenario, you can't really change the definition of the underlying primitive, so you can't really run into issues there.
- throw156754228 2y agoFeels hacky as hell, with the string literal in there.
- IshKebab 2y agoThe string literal doesn't exist at runtime.
- throw156754228 2y agoI know. I'm talking about its lack of elegance.
- IshKebab 2y agoStrings literals are proper singleton types in Typescript. It's not inelegant at all.
- recursive 2y agoYou could use a unique symbol as well.
- TOGoS 2y agoI like to make the 'brand' property optional so that it doesn't actually have to be there at runtime, but the TypeScript type-checker will still catch mismatches type USDollars = number & { currency?: "USD" } (or something like that; My TypeScript-fu may be slightly rusty at the moment.) Of course a `number` won't have a currency property, but if it did, its value would be "USD"! I've found that TypeScript's structural typing system fits my brain really well, because most of the time it is what I want, and when I really want to discriminate based on something more abstract, I can use the above trick[1], and voila, effectively nominal types! [1] With or without the tag being there at runtime, depending on the situation, and actually I do have a lot of interface definitions that start with "classRef: " + some URI identifying the concept represented by this type. It's a URI because I like to think of everything as if I were describing it with RDF, you see. (More of my own blathering on the subject from a few years ago here: http://www.nuke24.net/plog/32.html http://www.nuke24.net/plog/32.html)
- williamdclt 2y agoMaking it optional doesn't work, the brand property needs to be required. Doesn't mean that you actually have to define the property at runtime, you'd usually cast the number to USDollars where relevant
- TOGoS 2y agoAccording to Deno it does work. type USD = number & { currency?: "USD" } type CAD = number & { currency?: "CAD" } const fiveUsd : USD = 5; const fiveCad : CAD = fiveUsd; console.log("How many CAD? " + fiveCad); Results in an error, Type '"USD"' is not assignable to type '"CAD"'. If by "doesn't work" you mean that the implicit number->USD conversion is allowed, then I disagree with the judgement, as that is by design. But once the values are in variables of more specific types, the type-checker will catch it.
- Animats 2y agoPascal worked that way all the time, and it was hated. You could have "inch" and "meter" version of integer, and they were not interchangeable. This was sometimes called "strong typing" It's interesting that in Rust, "type" does not work that way. I kind of expected that it would. But no, "type" in Rust is just an alternate name, like "typedef" in C.
- earleybird 2y agoNASA might have some thoughts on mixing inch and meter types :-) https://en.wikipedia.org/wiki/Mars_Climate_Orbiter https://en.wikipedia.org/wiki/Mars_Climate_Orbiter
- kevincox 2y agoBoth approaches are useful at different times. For example you wouldn't want to accidentally multiple a meter by a centimeter but you may want to provide std::io::Result<T> which is equivalent to Result<T, std::io::Error> but just a bit nicer to type. For example in Rust you can do: type Foo = Bar; Which is just an alias, interchangeable with Bar. Or you can do: struct Foo(Bar); Which is a completely new type that just so happens to contain a Bar.
- exceptione 2y agoIt is a form of strong typing because integer could be the length of your toe nail, a temperature or the seconds since the unix epoch. Sometimes you really want to make sure someone is not going to introduce billion dollar bugs, by making the type different from the underlying representation. In Haskell that would be sth like newtype Temperature = Int At other times, you just want to document in stead of forcing semantics. A contrived example: type AgeMin = Int type AgeMax = Int isAdmissible :: AgeMin -> AgeMax -> Bool isAdmissible :: Int -> Int -> Bool // less clear
- mc10 2y agoThe AgeMin/AgeMax example seems more of a deficiency due to a lack of named function parameters; it would be equally clear if it had a type signature (using OCaml as an example) of val is_admissible : min_age:int -> max_age:int -> bool
- dyeje 2y agoI had the displeasure of working with a Flow codebase that typed every string and int uniquely like this. I could see the benefit if you’re working on something mission critical where correctness is paramount, but in your average web app I think it just creates a lot of friction and busy work with no real benefit.
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- culi 2y agoThat's nice but it seems you're in search of a nominal type system within a structurally typed language. I'd posit that it's usually much better to step and try to approach the problem from the way the language lends itself to be solved rather than trying to hack it to fit your expectations
- mattstir 2y agoDo you mind elaborating on why this approach would be bad in general? It avoids the overhead of creating new classes and wrapping your objects when all you care about is the type-safety that the class would provide. How would you "approach the problem from the way the language lends itself to be solved"?
- plasticeagle 2y agoYou're going to hate that you did that when you want to write a function that prints out any hash, surely? We once did a similar thing in C++, creating types for values in different units. Complete nightmare, everyone hated it. We went back to using 'double' for all floating point values.
- conaclos 2y agoI don't understand why users of branded types use strings as brands. Also using a utility type makes unreadable the TypeScript errors related to this type. If I had to use branded types, I personally would prefer a different approach: declare const USER_BRAND: unique symbol type User = { name: string, [USER_BRAND]: undefined } This also allows subtyping: declare const PERSON_BRAND: unique symbol type Person = { name: string, age: number, [USER_BRAND]: undefined, [PERSON_BRAND]: undefined } Although this is sometimes convenient, I always find branded types too clever. I would prefer a dedicated syntax for nominal types. I made my own proposal: https://github.com/microsoft/TypeScript/issues/202#issuecomment-1412793902 https://github.com/microsoft/TypeScript/issues/202#issuecomm...
- AlienRobot 2y agoiirc this is why the hungarian notation existed. They had ints for columns and rows and they used a prefix on variable names to distinguish which ints were columns and which ints were rows. Though I may be misremembering.
- deleted 2y ago[deleted]
- rednafi 2y agoIt’s trivial to do with nominal type. In Python: ```Hash = NewType('Hash', str) def generate_hash(what: str) -> Hash: return Hash(f“hashed_{what}”)```
- hombre_fatal 2y agoWhat makes it trivial here isn't nominal vs structural typing but rather that Python has a utility function for this case while you have to implement it yourself in Typescript with a one-liner.
- stevage 2y agoHas something changed in TypeScript? There have been a few of these blog posts about branded types lately, but afaik, it's been possible to do this for years. One of the projects I work on is crying out for better support for nominal typing. It involves manipulating music notation, and there are so many things passed around that are all strings, but semantically different: a note ("A"), a pitched note ("A4"), a key ("A minor"), etc etc etc. Life would definitely be easier if I could just quickly and conveniently declare the types to be distinct. I do use the branded types thing, but it's a bit clunky and sometimes gets in the way. And the error messages you get aren't as clear as you would like.
- Swiffy0 2y agoThis to me seems like one of those things that makes me think why is this considered such a problem that it needs a "proper" solution like this. If I have a function written in TS which takes a string type parameter called a hash... isn't it already obvious what the function wants? Furthermore when the function in question is a hash checking function, it is working exactly as intended when it returns something like "invalid hash" when there is a problem with the hash. You either supply it a valid well formed hash and the function returns success, or you supply it any kind of non-valid hash and the function returns a failure. What is the problem? In case the function is not a hash checking function per se, but e.g. a function which uses the hash for someyhing, you will still need to perform some checks on the hash before using it. Or it could be a valid hash, but there is nothing to be found with that hash, in which case once again everything already works exactly as it should and you get nothing back. It's like having a function checkBallColor which wants you to supply a "ball" to it. Why would you need to explicitly define that you need to give it a ball with a color property in which the color is specified in a certain way? If someone calls that function with a ball that has a malformed color property, then the function simply returns that the ball's color property is malformed. You will, in most cases probably, have to check the color property in runtime anyway. I've used TS based graphics libraries and they often come with something like setColorHex, setColorRGB, etc. functions so that you know how the color should be given. If you supply the color in a wrong way, nothing happens and I think that is just fine. Sorry for the rant, but I just don't get some of these problems. Like... you either supply a valid hash and all is fine, or you don't and then you simply figure out why isn't it valid, which you will have to do with this branding system as well.
- AlienRobot 2y agoI'd understand if you were coding PHP without an IDE, but coding Typescript how do you get the argument order wrong?
- morning4coffe 2y agoInteresting approach to simulate nominal typing in TypeScript. It's like finding creative solutions to bridge the gap between structural and nominal typing. Nicely explained in the article!