|
Thanks for highlighting this bit, I also find it very interesting to think about! Here's a related property I've also discovered of 'advanced' type systems. In my understanding of how unification happens in systems like H-M, you first give every expression its own type variable, then perform a search to produce assignments to those that remain valid and leave the remainders generic. But as your type system gets fancier, and particularly in the presence of union types and subtyping, this search process can feel like "make whatever type judgements are necessary to make this program still compile". E.g. in code like let x = new Set();
x.add('hello');
x.add(3); // oops, meant to add the string '3'
f(x);
The inferencer can just reason "oh apparently x is Set<number|string>", and "oh apparently f() operates over all kinds of sets, Set<{}>". And especially when there's never a more specific type for things to "bottom out" on (like say f just forwards the set elements to JSON.stringify(), which accepts both string and number already), nothing will ever reveal to you that you actually wrote a bug.But then meanwhile even in Haskell you run into cases where the inferencer wasn't generic enough, like https://wiki.haskell.org/Monomorphism_restriction , so it's not even clear cut that you want the inferencer to be smarter or dumber. As my coworker says: as a programmer you have to be able to basically run the inferencer in your head, and that becomes very hard when the inferencer gets very smart. (See the RxJS bug in the above blog post.) |