Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type errors for Ramda cookbook methods mapKeys and renameBy #273

Closed
mtpultz opened this issue Oct 7, 2017 · 9 comments
Closed

Type errors for Ramda cookbook methods mapKeys and renameBy #273

mtpultz opened this issue Oct 7, 2017 · 9 comments
Labels

Comments

@mtpultz
Copy link

mtpultz commented Oct 7, 2017

I'm trying to figure out what the proper typings are for the Ramda cookbook method mapKeys so it will transpile without throwing an error due to typings on fn argument of the anonymous function.

Example

    import * as R from 'ramda';
    
    export const mapKeys = R.curry(
      (fn: ???, obj: { [k: string]: any } | { [k: number]: any }) =>
        R.fromPairs(
          R.map(
            R.adjust(fn, 0), // <--- Error: tried typings for `adjust`
            R.toPairs(obj) // Fixed by referencing typings of `toPairs`
          )
        )
    );

Error

Error is on fn:

Argument of type '{}' is not assignable to parameter of type '(a: string) => string'. Type '{}' provides no match for the signature '(a: string): string'.

Description

As you would expect you can solve similar issues like with obj by referencing the toPairs typings of { [k: string]: any } | { [k: number]: any }, but I tried using the adjust typings for fn of (a: any) => any with no luck.

A similar typings error occurs for another Ramda cookbook method renameBy that also uses adjust, so I thought maybe this was a typings issue with adjust, but the example below works.

R.adjust((a: any): any => {
  return 2;
}, 2);
@mtpultz mtpultz changed the title Type error for mapKeys and renameBy Ramda cookbook methods Type error for Ramda cookbook methods mapKeys and renameBy Oct 7, 2017
@mtpultz mtpultz changed the title Type error for Ramda cookbook methods mapKeys and renameBy Type errors for Ramda cookbook methods mapKeys and renameBy Oct 7, 2017
@KiaraGrouwstra
Copy link
Member

Typings have trouble with generics, yeah. I tried reminding them this was still an issue at microsoft/TypeScript#16072 (comment).

Anyway, glad you liked renameKeys!
If typings are having trouble though, you could try it with a less point-free version as well, e.g. (arr: [string, any]) => [fn(arr[0]), arr[1]] over the adjust line. I think fn should just be (s: string) => string type right?

@mtpultz
Copy link
Author

mtpultz commented Oct 7, 2017

Thanks for the reply @tycho01.

Yah definitely find renameKeys quite useful since it is declarative and self-documenting, so thanks if that's your addition to the Ramda cookbook :)

fn should be (s: string) => string, but using that just moves the error down into R.map:

Argument of type 'string[][]' is not assignable to parameter of type 'KeyValuePair<number, string | string[]>[]'. Type 'string[]' is not assignable to type 'KeyValuePair<number, string | string[]>'. Property '0' is missing in type 'string[]'.

image

My TypeScript isn't very strong yet, but from the typings included below for R.map and R.adjunct it looks like everything should line up just fine.

map<T, U>(fn: (x: T) => U, list: T[]): U[];
map<T, U>(fn: (x: T) => U, obj: Functor<T>): Functor<U>; // used in functors
map<T, U>(fn: (x: T) => U): (list: T[]) => U[]; // <--- matched overload
map<T extends object, U extends {[P in keyof T]: U[P]}>(fn: (x: T[keyof T]) => U[keyof T], obj: T): U;
map<T extends object, U extends {[P in keyof T]: U[P]}>(fn: (x: T[keyof T]) => U[keyof T]): (obj: T) => U;

adjust<T>(fn: (a: T) => T, index: number, list: T[]): T[];
adjust<T>(fn: (a: T) => T, index: number): (list: T[]) => T[]; // <--- matched overload

@KiaraGrouwstra
Copy link
Member

Oh, that makes sense. So what it's saying is if fn is (s: string) => string, that wouldn't match number keys. Perhaps changing the function to accept those would do it?

@mtpultz
Copy link
Author

mtpultz commented Oct 9, 2017

Hi @tycho01, thanks for the help. I tried setting fn to accept numbers as well, and a bunch of other variations using the type definitions for map and adjust, but each variation just jumps back and forth between an issue with R.map or fn regarding their typings.

By comparison it was easy to get R.rename to work using fn: (a: {}) => {}).

I think this is probably the most confusing thing with TypeScript which is impeding our full adoption of it into Node projects, and in this particular case means not using Ramda in Angular, or Node unless developed using native ES6/7/Next. I almost can't imagine living without type safety, but the time spent doing this is overwhelming since it isn't like a "normal" bug you can just track down.

@KiaraGrouwstra
Copy link
Member

@mtpultz: I sympathize. Particularly in functional programming, TypeScript unfortunately may still feel like more of a bolt-on type system than an innately typed language.

/cc @ikatyang: his issue appears to be that fromPairs rejects the input here as the binary tuple indices 0 / 1 obtained from toPairs go lost in the map operation, even if the operation (adjust here) is swapped out for identity. That's admittedly unfortunate, and makes the function a bit harder to use type-wise.

I can see the intentions w.r.t. type safety though. The most solution seems type-level heterogeneous map -- guess I should continue on microsoft/TypeScript#17961.

A short-term solution should involve a cast I guess...

@mtpultz
Copy link
Author

mtpultz commented Oct 11, 2017

Thanks for fixing that issue. I was looking at the committed fix out of interest and that is definitely outside my realm of understanding. Is it possible to consume the update on npm, or is microsoft/TypeScript#17884 blocking?

@ikatyang
Copy link
Member

@mtpultz

We're trying to send PRs to DefinitelyTyped, but unfortunately it's blocked, see #233.

@raine
Copy link

raine commented Feb 2, 2018

import * as R from 'ramda'

export const mapKeys = R.curry(
  (fn: (key: string) => string, obj: { [k: string]: any } | { [k: number]: any }) =>
    R.fromPairs(
      R.map(
        R.adjust<0, [string, any]>(fn, 0),
        R.toPairs(obj)
      )
    )
)

So should this work?

Getting src/map-keys.ts(7,9): error TS2558: Expected 1 type arguments, but got 2.

@ikatyang
Copy link
Member

ikatyang commented Feb 3, 2018

@raine Similar to #377, see #377 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants