4 ms·
The upside of type inference is that making small changes to types upstream doesn't necessary incur changes to code downstream. But the downside of type infere
by always_good 8y ago
The upside of type inference is that making small changes to types upstream doesn't necessary incur changes to code downstream.
But the downside of type inference is that it makes reading code harder and you may depend on an IDE to know what your intermediate types are. Especially wrt generics and higher-order types where intermediate function calls don't have a concrete type in their signature.
Nothing demonstrates this better than any time you've written code yourself only to immediately invoke the IDE tooling to inspect what the type is. If you didn't know, good luck to the person reading it in git diffs. I've had to git clone someone's Rust project recently just to follow the type transformation across a bunch of future/stream chains.
This isn't an argument against static typing but rather an argument against inference excess. There's a nice middle ground where asserting the concrete type in critical places makes the code more readable but also checks your own assumptions. Like any time you received Maybe<Maybe<Int>> where you wanted Maybe<Int>.
- dllthomas 8y ago> This isn't an argument against static typing but rather an argument against excess. And also an argument for exposing better tooling when reading git diffs :D
- Klathmon 8y agoA general rule of thumb I follow is to explicitly type your interfaces, and infer private internals. (I mean "interfaces" not referring to any specific language feature, but to the concept in general).
- steveklabnik 8y agoThis is sorta where we landed with Rust; you have to type out type signatures of function signatures, but not inside function bodies. It's not based on public/private lines, but it's a similar idea.
- deleted 8y ago[deleted]
- kjeetgill 8y agoAgreed! That's where Java 10 landed with var. Only local variables can have their types inferred but Fucntion signatures and fields need explicit types.
- YorkshireSeason 8y agotype inference is that it makes reading code harder I find the opposite to be the case. My syntax isn't polluted with all manner of superfluous syntactic noise like A a = new A How is that more readable than something like the following? a = new A There are edge cases, but type-inferred languages do allow you to add type annotations. Especially wrt generics Why is this a problem? The very essence of parametric polymorphism is that the do "the same thing" at all types. This was first shown with Reynolds' famous Abstraction Theorem [1], which has in the mean time, been generalised in many ways. [1] J. C. Reynolds, Types, abstraction, and parametric polymorphism.
- Klathmon 8y agoSure, in that simple case explicit types aren't helping, but take a look at this simple function (it's plain javascript): export const mergeItems = (keyExtractor, oldItems, newItems) => uniqBy(oldItems.concat(newItems), keyExtractor) What is keyExtractor? What are old and new items? You can probably guess that oldItems and newItems are an array of something, and you can probably infer from names and stuff that keyExtractor should extract a key from the items somehow, but aside from that you don't have much. If you didn't know what the lodash function "uniqBy" does, you wouldn't know how to use this without research. Contrast that with this below (Javascript with Flow type annotations): export const mergeItems = <Item> ( keyExtractor: (value: Item) => string, oldItems: Array<Item>, newItems: Array<Item>, ): Array<Item> => uniqBy(oldItems.concat(newItems), keyExtractor) With types it is a lot more explicit. Key extractor is given the item in the array, and should return a string key. Now you don't need to know or care about how "uniqBy" works, or the exact details of how you should or shouldn't use it, and if you try to pass 2 arrays with different items, it will yell at you because that's not right, something that type inference can't determine (at least not with Flow's typesystem)
- tetrep 8y agoIf types are specified in function definitions, then you'll find them in the docs, which you need to be reading in order to properly use a function in the first place. Finding out type definitions is made massively easier with an IDE, but even without one there's an extremely high chance any library you're using is going to at least have autogenerated docs sitting on disk somewhere or mirrored on a crappy website. Maybe you need to clone their repo and build the docs yourself, but even that's pretty unlikely as most language ecosystems have a package manager + docs website or man pages. I don't really develop anything without at least a third of my screen real estate dedicated to documentation and I don't see "RTFM" as a meaningful or undesirable barrier to programming correctly. > Now you don't need to know or care about how "uniqBy" works, or the exact details of how you should or shouldn't use it... I don't follow you. What if "uniqBy" only works properly on sorted lists? You'd never know that based on the type (unless you've got dependent types) and if you're not reading docs on how the function works (the types check/it compiles!) you're going to be in a world of hurt at runtime. Intuitive programming may be easier but it's a heck of a lot more dangerous unless your compiler is intuitive too (i.e. makes the same intuitive assumptions that you do). > ... and if you try to pass 2 arrays with different items, it will yell at you because that's not right, something that type inference can't determine (at least not with Flow's typesystem) That's entirely Flow's fault. There's no reason a "proper" type system couldn't deduce that the same type would be needed for both arrays, the `oldItems.concat()` call should have a type definition something like (functional for brevity): concat(Array<T>, Array<T>). Nothing ambiguous about T being the same type.
- kazagistar 8y ago> Nothing demonstrates this better than any time you've written code yourself only to immediately invoke the IDE tooling to inspect what the type is. Nit saying you arr wrong, but i just eant to point out that this happens even in languages like Java 8 which have no local type inference. Any time you chain a method from another method, you are skipping a type. Ditto for inferred generic parameters on methods. On the other hand, if you have strong enough types, it doesn't matter as much... if you don't know what the exact intermediate types are, you can still know it's correct based on the final type and the fact that it compiled, which is enough for a code review imho.
- saosebastiao 8y agoThis, and compiler performance, are my big problems. Unification can be very costly on large codebases. And for this reason, I wish my IDE had a hotkey that would automatically infer and then insert type annotations on an entire file (as opposed to a single statement). Usually, after writing and getting everything working, it's helpful to have everything annotated.