5 ms·
"Assertion Functions" are quite interesting. When we moved from Flow to TypeScript, this was the number 1 thing that frustrated us about TypeScript. The issue i
by munchor 7y ago
"Assertion Functions" are quite interesting. When we moved from Flow to TypeScript, this was the number 1 thing that frustrated us about TypeScript. The issue is that we had a lot of calls like this:
```
function assert(condition: boolean, message: string) {
if (!condition) {
throw new Error(message);
}
}
assert(x !== null, "x can't be null");
// Flow understands that x can't be null here because `invariant` throws an error otherwise, but TS doesn't
```
However, TypeScript couldn't understand that `x` can't be null after that call since it doesn't understand what `assert` does. For that reason, we had to write a codemod that replaced all of our `assert` calls with an if condition that threw an error inline.
The new `asserts` syntax is nice (it would be even better if TS could do this automatically like Flow though), and will be a much welcome update for us.
- deepersprout 7y agoI don't get Assert Functions. What are they good for? Maybe there is a use case I am not getting, but this function yell(str) { assert(typeof str === "string"); return str.toUppercase(); // ~~~~~~~~~~~ // error: Property 'toUppercase' does not exist on type 'string'. // Did you mean 'toUpperCase'? } function assert(condition: any, msg?: string): asserts condition { if (!condition) { throw new AssertionError(msg) } } Seems like the equivalent to function yell(str : String) { return str.toUppercase(); // ~~~~~~~~~~~ // error: Property 'toUppercase' does not exist on type 'string'. // Did you mean 'toUpperCase'? } But the latter has much less code and is clearer too. What am I missing?
- bsaul 7y agoI’m not sure that’s what you’re asking, but assertion are often used to trigger error in debug mode on runtime properties not covered by the type system (array having a min / max number of elements, code being executed in a background thread, etc..).
- deepersprout 7y agoI still don't get it. Please help me out. Why should the type system have anything to do with something like this? function something(num : number) { assertIsInRange(num); return num + 1; } function assertIsInRange(num: number, msg?: string): asserts num { if (num < 50 || num > 100) { throw new Error('not in range'); } } something(50); How is `asserts num` helping here?
- mceachen 7y agoIf your something function was `something (num: string|number)`, typescript can safely trust that`num` is a number in the block proceeded by your call to assert because it knows it must be a number, not a string.
- jiofih 7y agoThis, and undefined values. Will be of huge value for dealing with protobuf data, where all keys are optional.
- deepersprout 7y agoIf the `something` function had `(num: string | number)` I would not be able to call `assertIsInRange(num: number)` because `Argument of type 'string | number' is not assignable to parameter of type 'number'.` So If I wanted to do that I would have to if (typeof num === 'number') { assertIsInRange(num); } anyway. I think I still don't get it.
- bsaul 7y agoi’m not sure about typescript, but in many languages assertion have the natural behavior of being compiled out of release builds and kept only for debug mode. The fact that it’s a built in feature of the language also can sometimes helps the compiler infer what’s the properties of code after it. But theoretically , yes you could very well code some equivalent testing function yourself, except for the compiler inference part it would probably be equivalent.
- The_rationalist 7y agoI see you used flow, what do you think about nominally VS structurally typed classes?
- munchor 7y agoI don't have much of an opinion here since we don't use classes that much (we use object types for everything). I think I prefer structurally typed classes but mostly just because that makes them consistent with how objects work (structurally typed).
- anderskaseorg 7y agoI wish though that TS would verify that the assertion functions really check the things that they claim to check. At the moment you can write function assertIsString(x: number | string): asserts x is string { } and TS will just ignore the fact that you didn’t check anything. One shouldn’t have to introduce a giant hole in the type system just to be able to encapsulate checks that TS should be capable of understanding. I filed this as a bug to see if the developers agree: https://github.com/microsoft/TypeScript/issues/33743 https://github.com/microsoft/TypeScript/issues/33743
- VMG 7y agoif that were possible you wouldn't need the special annotation
- anderskaseorg 7y agoWhat do you mean? Without any special annotations, TS already understands that function test(x: number | string): void { x.toUpperCase(); } is an error and function test(x: number | string): void { if (typeof x === "number") throw new Error(); x.toUpperCase(); } is not. So it should just as easily be able to understand that function assertIsString(x: number | string): asserts x is string { } is an error and function assertIsString(x: number | string): asserts x is string { if (typeof x === "number") throw new Error(); } is not.
- VMG 7y agoif TS can recognize the difference between the last two, why have an `asserts` annotation at all?
- anderskaseorg 7y agoSo that one can extract out a repeated check such as if (typeof x === "number") throw new Error(); into a function.
- peq 7y ago> it would be even better if TS could do this automatically like Flow though That would imply that the same function signature could produce different type checking results at the call site. That is something I find extremely frustrating as you can no longer reason about type checking locally.