If flow typing is 'type narrowing' (the article kinda dragged on pulling in unrelated concepts so it lost me), then if anything, it is at least as good as pattern-matching/switch-blocks. At least in Typescript. That is because it works in switch statements and non-switch statements and provides the same guarantees. I don't get this argument.
Consider something like:
data: {pages: number} | null = f()
if (!data){
return 0
}
return data.pages + 5
Sure you can switch on the 'data' as well (exhaustive type-switches are a thing in Typescript too), but thats a syntactic preference. It surely is nice to get the same guarantees without _needing_ a switch statement.Maybe it's because you focus on typescript here. But typescript is in a position that other languages are not: it has to make things work within an existing ecosystem, in particular with an untyped language. Under these constraints, flow typing might be the best solution. But that doesn't mean it's generally "at least as good as pattern-matching/switch-blocks".
Btw, pattern matching and switch-blocks are very different things - maybe it make it easier for you to understand to dive into a language that has native pattern matching and no switch-blocks at all because from what you write it seems to me that you have not been exposed to pattern matching much yet.
Is it?
let data: Some(usize) = f();
match data {
Some(pages) => pages + 5,
None => 0
}
Seems pretty nice.Or if you want a more structurally similar code with a guard (and removing the useless bits):
let Some(pages) = f() else {
return 0
};
pages + 5Never said it's not. I'm just saying it's nice to have type narrowing everywhere since it includes block based switching/matching as well (what you're arguging is nice, which I'd agree)
You'll have to use your imagination here because in typescript narrowing works on all conditionals (ternaries, boolean switches, etc), so naturally there are many more examples where it's useful. It's easy to trivialize any specific example.
E.g. they're useful in JSX when you want to do a one-line type-safe boolean switches like: {node && <RenderNode node={node}/>}.
For example, consider this Rust snippet :
pub fn positive1(x: isize) -> Option<usize> {
if x > 0 { Some(x as usize) } else { None }
}
Unless I'm mistaken, without narrow typing, this cast would be impossible, and there would be no way to avoid the redundant runtime check caused by try_into().unwrap(), that is not even optimized unless you trick the compiler.same sized integer casts in Rusts are no-ops [0]; the conditional isn't type narrowing, it just avoids the cases where the cast would not preserve the same semantic value.
[0] https://doc.rust-lang.org/reference/expressions/operator-exp...
sealed interface Foo<T> permits Impl {} // union type
record Impl() implements Foo<String> {} // product type
<T> T m(Foo<T> foo) {
return switch(foo) {
case Impl impl -> "hello"; // flow typing T=String
};
}switch resp {
case Result(val, meta):
doStuff(val)
case Error(msg, code):
logger.main.debug(msg)
}Those two words live on different contexts, as unions are about a memory usage pattern, and sum types are about conceptual software design, but they often go together on the same language feature.
Sum types, unions (in the C sense), and union types are three distinct concepts.
Wikipedia's article confusingly merges the latter two concepts together but they're different thing. TypeScript[1] and Scala[2] have union types, but they have nothing like C's notion of "unsafely reuse the same bits in memory to be interpreted as different things".
[1]: https://www.typescriptlang.org/docs/handbook/2/everyday-type...
[2]: https://dotty.epfl.ch/docs/reference/new-types/union-types.h...
Just that he picks a language that doesn't support union-types. But that doesn't mean that flow typing would be necessary here - it means that the language(s) should support union-types and extend their pattern matching accordingly.
In fact, I would say that flow typing is almost like a workaround for missing pattern matching.