Reputation: 2214
I am trying to implement a sumPluck function. It will allow the caller to specify a property of type number belonging to an object in an array then sum them.
Example:
type A = { prop: number }
const arr: A[] = [{prop: 1}, {prop: 2}];
pluckSum("prop", arr); // 3
I know how to type a pluck but I can't seem to get my type to recognize that it is actually only dealing with number properties. This is what I have:
type PropertiesOfTypeNames<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyof T];
type PropertiesOfType<T, U> = Pick<T, PropertiesOfTypeNames<T, U>>;
type NumberProperties<T> = PropertiesOfType<T, number>;
const pluckSum = <T, K extends keyof NumberProperties<T>>(arr: T[], k: K) =>
pipe(
arr,
R.map(v => v[k]),
R.sum
);
I get an error under the map saying : Type 'T[string]' is not assignable to type 'number
So it doesn't seem that the mapped type communicates that v[k] is a number property. I must be doing something wrong here.
Upvotes: 2
Views: 304
Reputation: 2214
Figured it out. I just needed to add Record like this:
const pluckSum = <T extends Record<K, number>, K extends keyof NumberProperties<T>>(arr: T[], k: K) =>
pipe(
arr,
R.map(v => v[k]),
R.sum
);
Update 1 I guess I can further refine it by removing the mapped type "NumberProperties" since Record takes care of what I was trying to achieve anyways. So the final version is just:
const pluckSum = <T extends Record<K, number>, K extends keyof T>(k: K, arr: T[]) =>
pipe(
arr,
R.map(v => v[k]),
R.sum
);
Upvotes: 2
Reputation:
Regardless of Typescript I wanted to show you an alternative approach with loop fusion, i.e. elimination of the redundant array traversal. I gain loop fusion with a somewhat unorthodox combinator of type (b -> c) -> (a -> c -> d) -> a -> b -> d
(in Hindley-Milner notation), which I call contramap2nd
, because it contra-maps on the second argument of a binary function.
Long story, short story:
// (b -> c) -> (a -> c -> d) -> a -> b -> d
const contramap2nd = g => f => x => y =>
f(x) (g(y));
const arrFold = f => init => xs =>
xs.reduce((acc, x) => f(acc) (x), init);
const add = x => y => x + y;
const prop = k => o => o[k];
const pluckSum = k =>
arrFold(
contramap2nd(
prop(k))
(add)) (0);
console.log(
pluckSum("foo") ([{foo: 1}, {foo: 2}, {foo: 3}]));
You can do the contra-mapping on the second argument of add
by hand, of course, if you don't like contramap2nd
.
Please note that you can gain loop fusion with both map
, contramap
et al.
Upvotes: 2