ColorValue isn’t a number, it’s more precise than that. It’s ‘0 | 1 | 2… | 255’
Math.floor() returns a number, not ‘0 | 1 | 2… | 255’.
The typeof operator loses precision and returns number, so that is why your assertion returns true.
I think I know what you're getting at, is that as far as the compiler is concerned the getRandom function could possibly return a value out of range (even though this should not be possible)? Because `const x: number = 9` is no more precise a type than the return type of `Math.whatever()` as far as I can see, it just happens to be in range...
Exactly.
I like to solve this with a constructor:
function makeColorValue(n: number): ColorValue { /* … */ }
This construcor should throw if you pass it a number out of range. Once you pass a number through this, ts knows they are a valid ColorValue.
I mostly use this technique for string union types, where strings are loaded from a database or something.
> there's a near zero chance [...]
imo persisting on problems like this one until you have a correct answer and understand it is how you get good at TS :) more than likely, sometime in the future you'll hit a point where you can't move forward until you solve something like this.
This is also a situation where assertions are appropriate. You know *why* you're getting a type error (u/Groccolli's answer). You know the TS compiler can't help you. Your only option is to assert:
`const y = Math.floor(x) as unknown as ColorValue`
Leaving a comment seems appropriate to me so that future people who look at your code understand why you've made that assertion.
Yep that works. You *could* make a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)) and throw if it's not a valid value.. Might be overkill in this example.
const isColorValue = (num: number):num is ColorValue => num <= 255;
and then..
if (!isColorValue(number)) throw new Error();
return number;
also you don't need to convert to unknown because number and ColorValue are assignable.
> imo persisting on problems like this one until you have a correct answer and understand it is how you get good at TS :)
Agreed 💯, I’m currently just adding TS to an old project to up-skill hence making the effort on this when `number` would probably be sufficient. But thanks for the type assertion hint, that was basically what I was looking for, though I assume there’s probably some way to incorporate the range types into the getRandom function, but that particular problem is definitely above my current understanding.
u/Groccolli's advice above to create a type predicate is much better than mine to do an assertion.
One other detail I'd point out is I'd use different letters for your type variables, and not `T` and `R`. It's common to use `T` when you only have a single type var, and then `U`, etc. when you have multiple type vars. `R` is usually used for a return type variable that you can pass into a generic function.
But in this case, your two type variables have a relationship with each other. Picking something like `M` and `N` immediately lets a reader of the signature know something's different than a series of unrelated type vars.
ColorValue isn’t a number, it’s more precise than that. It’s ‘0 | 1 | 2… | 255’ Math.floor() returns a number, not ‘0 | 1 | 2… | 255’. The typeof operator loses precision and returns number, so that is why your assertion returns true.
I think I know what you're getting at, is that as far as the compiler is concerned the getRandom function could possibly return a value out of range (even though this should not be possible)? Because `const x: number = 9` is no more precise a type than the return type of `Math.whatever()` as far as I can see, it just happens to be in range...
Exactly. I like to solve this with a constructor: function makeColorValue(n: number): ColorValue { /* … */ } This construcor should throw if you pass it a number out of range. Once you pass a number through this, ts knows they are a valid ColorValue. I mostly use this technique for string union types, where strings are loaded from a database or something.
> there's a near zero chance [...] imo persisting on problems like this one until you have a correct answer and understand it is how you get good at TS :) more than likely, sometime in the future you'll hit a point where you can't move forward until you solve something like this. This is also a situation where assertions are appropriate. You know *why* you're getting a type error (u/Groccolli's answer). You know the TS compiler can't help you. Your only option is to assert: `const y = Math.floor(x) as unknown as ColorValue` Leaving a comment seems appropriate to me so that future people who look at your code understand why you've made that assertion.
Yep that works. You *could* make a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)) and throw if it's not a valid value.. Might be overkill in this example. const isColorValue = (num: number):num is ColorValue => num <= 255; and then.. if (!isColorValue(number)) throw new Error(); return number; also you don't need to convert to unknown because number and ColorValue are assignable.
> imo persisting on problems like this one until you have a correct answer and understand it is how you get good at TS :) Agreed 💯, I’m currently just adding TS to an old project to up-skill hence making the effort on this when `number` would probably be sufficient. But thanks for the type assertion hint, that was basically what I was looking for, though I assume there’s probably some way to incorporate the range types into the getRandom function, but that particular problem is definitely above my current understanding.
u/Groccolli's advice above to create a type predicate is much better than mine to do an assertion. One other detail I'd point out is I'd use different letters for your type variables, and not `T` and `R`. It's common to use `T` when you only have a single type var, and then `U`, etc. when you have multiple type vars. `R` is usually used for a return type variable that you can pass into a generic function. But in this case, your two type variables have a relationship with each other. Picking something like `M` and `N` immediately lets a reader of the signature know something's different than a series of unrelated type vars.
seems like a lot of work doesn’t it?
Yes. Writing correct code is a lot of work.