11

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.

0

2 Answers 2

7

There are two errors:

  • You need to provide which type is array. You can't get it from single generic Array<T>, you need to introduce both T and Array<T>.
  • Your type for function that is consumed by 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

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. My use case was different, but your answer reminded me that I needed to type my accumulator, which made everything good to go!
2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.