then you're creating a giant mess of a soup where the state of your program could have a result, be loading and an error at the same time. If you could recognise that the state of your program is a sum of possible states (loading | success | error), and not their product as the type above you could highly simplify your code, add more invariants and reduce the number of bugs.
And that is a very simple and basic example, you can go *much* further, as in encoding that some type isn't merely a number through branded types, but a special type of number, be it a positive number between 2 and 200 or, being $ or celsius and avoiding again and entire class of bugs by treating everybody just as an integer or float.
For a function setVelocity() that can accept 1..<200. You call it with numbers that you enter directly and types tell you something that would otherwise be a comment on the function, or you do runtime checks elsewhere, and the type becomes proof that you checked them before handing it into the function.
Btw, using “autism” to mean “pedantry” leaves a bit of a bad taste in my mouth. Maybe you could reconsider using it that way in the future.
Pushing everything to types like this creates a different burden where you're casting between types all over the place just to use the same underlying data. You could just clamp velocity to 200 in the callee and save all that hassle.
> Pushing everything to types like this creates a different burden where you're casting between types all over the place just to use the same underlying data.
TypeScript does not perform any kind of casting at all. What TypeScript supports is structural typing, which boils down to allowing developers to specify type hints in a way that allows the TypeScript compiler to determine which properties or invariants are met in specific code paths.
Literal types address a very common and very mundane use case: assert what can and cannot be done with an object depending on what value one of it's fields have.
Take for example authorization headers. When they are set, their prefix tells you which authorization scheme is being used by clients. With typescript you can express those strings as a prefix constrained string type, and use them to have the TypeScript compiler prevent you from accidentally pass bearer tokens to the function that handles basic authentication.
Literal types shine when you are using them to specify discriminant fields in different types. Say for example you have a JSON object that has a `version` field. With literal types you can define different types discriminated by what string value features in it's `version` field, and based on that alone you can implement fully type-safe code paths.
If you have some `ConstrainedNumber` type, you will need to cast between it and `number`, either with a type assertion or with a type guard. In either case, when you use bespoke types everywhere you kill code reuse.
Casting? Not really - i think you’d only need a couple type checks.
Imo this is mostly useful for situations where you want to handle input validation (and errors) in the UI code and this function lives far away from ui code.
Your point about clamping makes sense, and it’s probably worth doing that anyway, but without it being encoded in the type you have to communicate how the function is intended to be used some other way.
Ah, yeah you’re right. I somehow thought typescript could do type narrowing based on checks - like say:
If (i >= 1) {
// i’s type now includes >= 1
}
But that is not the case, so you’d need a single cast to make it work (from number to ClampedNumber<1,200>) or however exactly you’d want to express this.
Tbf having looked more closely into how typescript handles number range types, I don’t think I would ever use them. Not very expressive or clear. I think I hallucinated something closer to what is in this proposal: https://github.com/microsoft/TypeScript/issues/43505
I still think that the general idea of communicating what acceptable input is via the type system is a good one. But the specifics of doing that with numbers isn’t great in typescript yet.
How would you implement it in other languages that support it better? Can you literally just do a range check and the compiler infers its range for types? If so thats actually pretty neat.
Yeah, that’s probably the best possible implementation outside of the type system. The issue with docstrings is that they aren’t checked. So you’ve communicated the api to the coder, and maybe even to the ide, but not to the compiler.
If you type some state as:
then you're creating a giant mess of a soup where the state of your program could have a result, be loading and an error at the same time. If you could recognise that the state of your program is a sum of possible states (loading | success | error), and not their product as the type above you could highly simplify your code, add more invariants and reduce the number of bugs.And that is a very simple and basic example, you can go *much* further, as in encoding that some type isn't merely a number through branded types, but a special type of number, be it a positive number between 2 and 200 or, being $ or celsius and avoiding again and entire class of bugs by treating everybody just as an integer or float.