lodash type defs / generics inference question

Topics: Language Specification
Apr 24, 2014 at 5:08 AM
Edited Apr 24, 2014 at 5:13 AM
This question relates to https://typescript.codeplex.com/workitem/2239.

I've encountered the same issue with the lodash type definitions. There's a Collection<T> interface declared here: https://github.com/borisyankov/DefinitelyTyped/blob/01f1a715b4ad1906e89f85a330ae972a70c533b7/lodash/lodash.d.ts#L3856.

lodash has a find function declared as follows
find<T>(collection: Collection<T>, pluckValue: string): T;
When calling find, it appears that TS does not detect the return type correctly (VS thinks it is "{}").

I attempted to add an optional param referring to T within the Collection<T> interface (as suggested in 2239), but it didn't seem to change anything. I can't really add a required param as it won't exist on both arrays and objects.

I also tried adding an indexer to Collection<T> that takes an "any" key and returns a T. TS doesn't seem to allow "any" to be used as an indexer though.

The only solution I can see is instead of declaring the methods as accepting a Collection<T>, to provide two overloads (one that accepts List<T> and one that accepts Dictionary<T>). This doesn't seem ideal, so I'm curious if there are any better solutions.
Coordinator
Apr 24, 2014 at 4:54 PM
The T might be inferred to {} if the collection you're passing can't be resolved to something with more information.

Can you share the code where you're calling 'find'? We might be able to tell why the type is being inferred to {}.
Developer
Apr 24, 2014 at 7:18 PM
The lodash/underscore typing on DefinitelyTyped needs some significant updates to work better with TypeScript's type inference and generic inference algorithms. That Collection<T> {} is so pervasive is a real problem. Not only is that type definition meaningless (literally any object qualifies as a Collection<T>, it is no better than saying 'any' and worse due to the confusion it creates) but it also can't be inferred correctly since there's no data to inform what T actually is. There are numerous functions with similar issues where T can rarely if ever be inferred (ex functions of the form foo<T>(): T {}).

For example, note that this isn't an error:
_.find(1, 'ummm');
after which you might be less surprised that this doesn't work correctly
var r = _.find([1,2], '1'); // r is {} not number
while explicitly specifying the type argument does work
var r = _.find<number>([1, 2], '1'); // r is number
You are on the right track with indexers. A string indexer is sufficient for what you are trying to do to Collection. Currently the List and Dictionary types are far more useful as they actually specify what makes something List-like or Dictionary-like (via indexers), while Collection says literally nothing about what makes something Collection-like. The type hierarchy appears to have been written with a nominal sense of typing in mind rather than a structural one, as Collection<T> not only serves no purpose but actually makes the typings less likely to generate the errors you want it to catch.
Apr 24, 2014 at 9:00 PM
Edited Apr 24, 2014 at 9:02 PM
The lodash definitions were using the type inference before so that when using Collection<T> it would infer to List<T> or Array<T> or Dictionary<T> before the type inference was removed (pre-0.9.7). This also was used to imply Array<T> was compatible with List<T>. This allowed support for JQuery objects to be used, as well as Arrays, as well as object indexers to simplify the typings.

I was never able to figure out a way to have that implicit resolution work, and the issues on the definietely typed repo were just hoping that pre-0.9.7 type resolution would return. I have an active pull request against the respository that just breaks out the use of Collection<T> => Array<T>, List<T>, Dictionary<T> and instances of List<T> => Array<T>, List<T>. With these changes most of the functionality is restored, the only difference is there are a hell of a lot more lines to maintain. That said I'll take more lines to maintain, and have a working type definition for a library that I use with almost all my projects.
Apr 25, 2014 at 12:49 AM
Thanks for the heads up, David. I'll check out your fork and probably end up using your type defs for now. I agree that type safety is the better trade-off (especially if someone else is maintaining it for you :)). We can always switch back if a better way to write the defs avails itself.