Typescript type errors on Ramda Transducers & how to deal with confusing type errors on good code

1k Views Asked by At

Looking at the Ramda documentation for transduce, there are two examples given, each of which cause the Typescript compiler to throw a different error.

Example 1:

test('ex. 1', () => {
  const numbers = [1, 2, 3, 4]

  const transducer = compose(
    map(add(1)),
    take(2)
  )

  const result = transduce(transducer, flip(append), [], numbers)

  expect(result).toEqual([2, 3])
})

Typescript throws the following exception for flip(append):

Argument of type '(arg1: never[], arg0?: {} | undefined) => <T>(list: readonly T[]) => T[]' is not assignable to parameter of type '(acc: ({} | undefined)[], val: {} | undefined) => readonly ({} | undefined)[]'.
      Types of parameters 'arg1' and 'acc' are incompatible.
        Type '({} | undefined)[]' is not assignable to type 'never[]'.
          Type '{} | undefined' is not assignable to type 'never'.
            Type 'undefined' is not assignable to type 'never'.

If I change flip(append) to flip(append) as any the code works as expected.

Example 2:

test('ex. 2', () => {
  const isOdd = x => x % 2 === 1
  const firstOddTransducer = compose(
    filter(isOdd),
    take(1)
  )

  const result = transduce(
    firstOddTransducer,
    flip(append) as any,
    [],
    range(0, 100)
  )

  expect(result).toEqual([1])
})

Typescript throws the following exception for firstOddTransducer:

Argument of type '(x0: readonly any[]) => Dictionary<any>' is not assignable to parameter of type '(arg: any[]) => readonly any[]'.
      Type 'Dictionary<any>' is missing the following properties from type 'readonly any[]': length, concat, join, slice, and 16 more.

Same as above, if I change firstOddTransducer to firstOddTransducer as any the code works as expected.

First, what do these particular errors even mean?

Second, what is the best way to deal with these sorts of issues with functional typescript? So often, when looking at various learning resources for typescript, users are warned against using any or against using // @ts-ignore as if it something you should never do, but the more complex my codebase gets, and the more functional my programming style becomes, the more of these seaming incomprehensible errors messages I receive for perfectly acceptable code. I don't mind spending a little bit of my time to make the types better, but I don't want to spend too much time debugging problems with types when I know the code is good.

Third, are there any tips that can help in situations when you're not quite sure if there is a problem with the types or with typescript, as above, or if there is a problem with the javascript code, e.g., techniques for determining where the actual problem is so you can either investigate or ignore?

1

There are 1 best solutions below

0
John Leidegren On

As of writing, this is a limitation of TypeScript, there's simply no way for you to express that part of the type has to be inferred from later use.

It isn't until you call transduce that you actually bind all the type parameters so, it isn't a complete type until then, or, rather, TypeScript doesn't know how to complete the type until you do that. TypeScript will attempt to infer the type from context, so maybe, if you put all that on a single line it might be able to do that (not saying it will).

Fundamentally a transducer is an abstraction of a sequence/stream/observable and you grant the compiler this information when you actually run your transducer over some data. That's when it needs to bind the type information of the data.

The only thing I can think of here is that you stick unknown as a placeholder type of the data until you run transduce or into. It won't grant you type safety but it will suppress the compiler warnings which either way are false positives. It's not perfect but it should work.