Say I have the following foldable interface:
export interface Foldable<F> {
reduce: <A>(fn: (b: A, a: A) => A, initial: A, foldable: F) => A;
}
And then I want to implement it for array:
export const getArrayFold = <A>(): Foldable<Array<A>> => {
return {
reduce: (fn, initial, array) => {
return array.reduce(fn, initial);
}
};
};
But the compiler complains with:
Argument of type '(b: A, a: A) => A' is not assignable to parameter of type '(previousValue: A, currentValue: A, currentIndex: number, array: A[]) => A'. Types of parameters 'a' and 'currentValue' are incompatible. Type 'A' is not assignable to type 'A'. Two different types with this name exist, but they are unrelated.
I don't understand how there are two different types of A
here.
There are two errors:
Array<T>
, you need to introduce both T
and Array<T>
.reduce
is not adequate. Correct one: (previousValue: A, currentValue: F) => A
Explanation:
If you provide initial value with type (e.g. string
) to reduce function, previousValue parameter is always same as inital.
See official TypeScript reduce declaration:
interface Array<T> {
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => U, initialValue: U): U;
}
Full code (refactored)
interface Foldable<F, T> {
reduce: <A>(
fn: (previousValue: A, currentValue: T) => A,
initial: A,
foldable: F
) => A;
}
const getArrayFold = <T>(): Foldable<T[], T> => ({
reduce(fn, initial, array) {
return array.reduce(fn, initial);
}
});
// Real implementation usage
const array: number[] = [1, 2, 3]
const initial: string = "";
const fn: (previousValue: string, currentValue: number) => string = (a, b) => a + b;
const newValue: string = getArrayFold().reduce(fn, initial, array);
See code on TypeScript playground
It's easier to see what is going on if you change the generic type names:
export const getArrayFold = <R>(): Foldable<Array<R>> => {
Now you will get Type 'R' is not assignable to type 'A'
. array.reduce
uses different types for the current value and previous value, so you have type A
(the generic type from your interface) and type R
from your getArrayFold
function.
You have not actually passed in the generic type A
to reduce
, so it considers it to be A
from the interface which essentially just means it can't determine what the type is supposed to be.
One way I have found to do this is to allow your interface to specify the type of both A
and F
:
export interface Foldable<F, A> {
reduce: (fn: (b: A, a: A) => A, initial: A, foldable: F) => A;
Now you can write your array function as
getArrayFold = <R>(): Foldable<Array<R>, R>
When you call it, you can do
getArrayFold<string>().reduce((a, b) => a + b, '', ['hello', 'world']);
This will give you type safety so you can't use 0
as a value or .toFixed
on the a
/b
properties or things like that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With