Definitelytyped: [@types/ramda] map on object not supported

Created on 6 Sep 2019  路  5Comments  路  Source: DefinitelyTyped/DefinitelyTyped

If you know how to fix the issue, make a pull request instead.

  • [x ] I tried using the @types/ramda package and had problems.
  • [ x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [x ] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [ x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @donnut @mdekrey @mrdziuban @sbking @afharo @teves-castro @1M0reBug @hojberg @samsonkeung @angeloocana @ethanresnick

// this gives compile error
const res1 = map((): string => 'wow', { someProp: 'uhh' });
// this compiles fine
const res2 = map((): string => 'wow', ['uhh']);
src/wrap.ts:34:14 - error TS2769: No overload matches this call.
  Overload 1 of 6, '(fn: (x: unknown) => string, list: readonly unknown[]): string[]', gave the following error.
    Argument of type '{ someProp: string; }' is not assignable to parameter of type 'readonly unknown[]'.
      Object literal may only specify known properties, and 'someProp' does not exist in type 'readonly unknown[]'.
  Overload 2 of 6, '(fn: (x: never) => never, list: { someProp: string; }): unknown', gave the following error.
    Type 'string' is not assignable to type 'never'.
  Overload 3 of 6, '(fn: (x: unknown) => string, obj: Functor<unknown>): Functor<string>', gave the following error.
    Argument of type '{ someProp: string; }' is not assignable to parameter of type 'Functor<unknown>'.
      Object literal may only specify known properties, and 'someProp' does not exist in type 'Functor<unknown>'.

34 const res1 = map((): string => 'wow', { someProp: 'uhh' });
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  node_modules/@types/ramda/index.d.ts:1768:23
    1768         map<T, U>(fn: (x: T[keyof T & keyof U]) => U[keyof T & keyof U], list: T): U;
                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    The expected type comes from the return type of this signature.

[1:03:50 PM] Found 1 error. Watching for file changes.

All 5 comments

It is typed, TypeScript just cannot differentiate between the types so you need to explicitly tell it which version of map you are using:

R.map<{[key: string]: string},{[key: string]: string}>(() => 'wow', { someProp: 'uhh' });

or, more specific

R.map<{someProp: string},{someProp: string}>(() => 'wow', { someProp: 'uhh' });

@dbartholomae How could I typing map in this case?

const initials = R.compose(
    R.join('. '),
    R.map(R.compose(R.toUpper, R.head)),
    R.split(' ')
)

Explicitly typing map seems excessive and cumbersome, it is easier to simply replace it with mapObjIndexed to prevent ambiguity.
I think there might be some easier solution in the future, but for now it seems reasonable.

The typings for map on objects seem to contain a mistake:

export function map<T, U>(fn: (x: T[keyof T & keyof U]) => U[keyof T & keyof U], list: T): U;
export function map<T, U>(fn: (x: T[keyof T & keyof U]) => U[keyof T & keyof U]): (list: T) => U;

This leads to problems when T is a union. T[keyof T] retains only the keys that are present across all types in the union. I think this can be resolved with the following:

/** @see https://stackoverflow.com/a/60085683 */
type KeyOfUnion<T> = T extends infer U ? keyof U : never

export function map<T, U>(fn: (x: KeyOfUnion<T>) => U[keyof T & keyof U], list: T): U;
export function map<T, U>(fn: (x: KeyOfUnion<T>) => U[keyof T & keyof U]): (list: T) => U;

I鈥檓 not extremely proficient in TypeScript. Perhaps someone can confirm whether I should file a PR like this?

I, too, get effectively un-compiling map use with plains objects, even without any unions and the most recent master of @types/ramda. The following does not compile for me:

declare function identity<T>(value: T): T

const myMap = {
  one: 1,
  two: 2,
  three: 3
} as const

map(identity, myMap)
//=> Property 'map' is missing in type '{ readonly one: 1; readonly two: 2; readonly three: 3; }' but required in type 'Functor<unknown>'

map(identity)(myMap)
//=> Argument of type '{ readonly one: 1; readonly two: 2; readonly three: 3; }' is not assignable to parameter of type 'readonly unknown[]'

In the first case, it seems Typescript does not recognize myMap as an object but as a Functor. In the second case, myMap is treated as an array.

I seem to be able to get correct type inference by adding the following overload signatures before the existing ones though:

declare function map<T extends Record<string, unknown>, U>(fn: (x: T[keyof T]) => U, obj: T): Record<keyof T, U>
declare function map<T extends Record<string, unknown>, U>(fn: (x: T[keyof T]) => U): (obj: T) => Record<keyof T, U>

This Typescript playground demonstrates the issue and includes the above proposal. Commenting the proposed overloads reveals the type errors mentioned here.

Does this proposal work for anyone else as well? Should I submit this as a PR to have these overloads added to map's signature?

Was this page helpful?
0 / 5 - 0 ratings