@types/ramda
package and had problems.Authors: @donnut @mdekrey @mrdziuban @sbking @afharo @teves-castro @1M0reBug @hojberg @charlespwd @samsonkeung @angeloocana @raynerd @googol @moshensky @ethanresnick @leighman @CaptJakk
Hi. I'm trying to use R.pipe
with R.filter
, but I couldn't get the typing to work. But if I ignore the typings by adding // @ts-ignore
the code works. I'm not asking this on StackOverflow because I'm almost sure it's a bug on the typings.
This doesn't work (the typings, the code is great, as ignoring the error part with // @ts-ignore makes it work
const searchRegexp = /\.dev\.test\.io/;
const env = 'prod';
const data = [
'https://testing.dev.test.io',
'https://testing2.local.dev.test.io',
'https://testing3.dev.test.io',
];
const values = pipe<string[], string[], string[], string[]>(
map(v => v.trim()),
/*
Below line throws:
Argument of type 'Filter<{}>' is not assignable to parameter of type '(x: string[]) => string[]'.
Type '{}[]' is not assignable to type 'string[]'.
Type '{}' is not assignable to type 'string'. (2345)
*/
filter(v => !(contains('.local.', v) && env !== 'dev')),
map(v => v.replace(searchRegexp, env === 'prod' ? '.test.com' : `.${env}.test.io`)),
)(data.split(','));
This works (both typings & code)
const searchRegexp = /\.dev\.test\.io/;
const env = 'prod';
const data = [
'https://testing.dev.test.io',
'https://testing2.local.dev.test.io',
'https://testing3.dev.test.io',
];
const values = pipe<string[], string[], string[], string[]>(
map(v => v.trim()),
// adding this extra anonymous function here, makes everything to work
list => filter(v => !(contains('.local.', v) && env !== 'dev'), list),
map(v => v.replace(searchRegexp, env === 'prod' ? '.test.com' : `.${env}.test.io`)),
)(data.split(','));
I'm not sure this is a bug in the typing so much as a deficiency in the type inference. I can look into what is causing this but there is a chance you may have to parameterize filter.
Any response on this? I really think that I'm not using the types incorrectly.
This is also a big problem for us, even very simple things like:
import { pipe, filter } from 'ramda';
const input = [1,2,3,4];
const output = pipe(
filter((n: number) => n % 2 === 0)
)(input);
do not compile:
Argument of type 'number[]' is not assignable to parameter of type 'Dictionary<number>'.
Index signature is missing in type 'number[]'.
The current typing for for filter
is:
filter<T>(fn: (value: T) => boolean): Filter<T>;
filter<T>(fn: (value: T) => boolean, list: ReadonlyArray<T>): T[];
filter<T>(fn: (value: T) => boolean, obj: Dictionary<T>): Dictionary<T>;
For arrays the following typing is already fixing the issue:
filter<T>(fn: (value: T) => boolean, list: ReadonlyArray<T>): T[];
filter<T>(fn: (value: T) => boolean): (list: ReadonlyArray<T>) => T[];
This problem renders usage of ramda in TS pretty much unusable in some situations...
Obviously, the same applies for the reject
function and similar fix works too:
reject<T>(fn: (value: T) => boolean, list: ReadonlyArray<T>): T[];
reject<T>(fn: (value: T) => boolean): (list: ReadonlyArray<T>) => T[];
Here's a proposal:
We add a second type parameter to filter
and reject
, that specifies whether the list or object input is expected. The list is probably most commonly used so we could make that the default.
type Dictionary<T> = Record<string, T>;
type Filter<T, kind extends 'list' | 'dictionary'> = kind extends 'list' ? (list: ReadonlyArray<T>) => T[]
: kind extends 'dictionary' ? (obj: Dictionary<T>) => Dictionary<T>
: never;
declare function filter<T, kind extends 'list' | 'dictionary' = 'list'>(fn: (value: T) => boolean): Filter<T, kind>;
declare function filter<T>(fn: (value: T) => boolean, list: ReadonlyArray<T>): T[];
declare function filter<T>(fn: (value: T) => boolean, obj: Dictionary<T>): Dictionary<T>;
declare function pipe<T1, T2, R>(f1: (t: T1) => T2, f2: (t: T2) => R): (t: T1) => R;
pipe(
filter((val: string) => val.length > 2),
(x) => x.length
)(['list', 'or', 'object'])
pipe(
filter<string, 'dictionary'>((val) => val.length > 2),
(x) => x.length
)({ a: 'list', b: 'or', c: 'object' })
I have a branch for this here: https://github.com/googol/DefinitelyTyped/tree/ramda/filter-fix but I'll need to check the tests before making a PR. There seem to be tests that should catch the original problem already, but they aren't most likely making the correct assertions.
@googol Hey,
if I understand correct, your change merged with #28592 breaks the following "hack":
declare module 'ramda' {
interface Static {
filter<T>(fn: (value: T) => boolean): (list: ReadonlyArray<T>) => T[];
filter<T>(fn: (value: T) => boolean, list: ReadonlyArray<T>): T[];
}
}
Error:
.../ramda.d.ts:20:5 - error TS2300: Duplicate identifier 'filter'.
20 filter<T>(fn: (value: T) => boolean, list: ReadonlyArray<T>): T[];
Of course I can fix the issue now via removing my custom types and adding the Kind
generic, but this is a breaking change not everyone is willing to make I suppose.
Are my assumptions correct? Do you see any way to fix this problem?
Honestly, to me personally that the situation is now worse than before the #28592. Do not get me wrong, it is really nice that people care about this project and maintain the types, so thanks for the contribution! I just wanted to point out potential problems with the current solution. What is you opinion on that?
Update: what I would basically like to have is the following code compiling:
import { filter, pipe } from 'ramda';
const arr = [0, 1, 2, 3];
const fn = pipe(
filter(x => !!x)
);
fn(arr);
which worked before with the mentioned "hack".
@bzums you'd have to override Filter
interface, not methods on Static
.
import R from 'ramda';
declare module 'ramda' {
interface Filter {
/* overrides */
}
}
Oh yeah of course, thanks!
So for the sake of documentation, this solves our issue currently:
declare module 'ramda' {
interface Filter {
<T>(fn: (value: T) => boolean): (list: ReadonlyArray<T>) => T[];
<T>(fn: (value: T) => boolean, list: ReadonlyArray<T>): T[];
}
}
I'm running into this problem also, but not entirely sure how the fix works. It seems once you call filter(fn)
it returns a new function with interface FilterOnceApplied<T>
. The new interface is for a function overloaded with 2 method signatures:
interface FilterOnceApplied<T> {
(list: ReadonlyArray<T>): T[];
(obj: Dictionary<T>): Dictionary<T>;
}
So far as I can tell, the first signature is not causing any issue. It's only the second that causes problems. If I remove the second signature from the interface, the ts error vanishes.
Anyone with a good understand of the mechanics at play here able to help me understand how this works?
For anyone having this problem, I solved it with out overriding ramda's reject interface, I just did this:
reject<string, 'object'>(propEq('id', id)),
Actually, there is a type helper: <T, Kind extends 'array'>(fn: (value: T) => boolean): (list: readonly T[]) => T[];
Filter used like this should have a correct return type: pipe(filter<MyType, 'array'>(myFunction))(myArray)
. This is because filter is supposed to work with both arrays and objects, but typescript gets confused as a result.
I've found this issue because I've found current typings for filter
and reject
do not fully support type guards. It would be good to extend existing types with support for filtering functions which are type guards and should narrow the result type. I've wrote sample typings for a use case with array:
type ArrayFilter = <T2 extends T1, T1>(fn: (value: T1) => value is T2) => (list: readonly T1[]) => T2[];
type ArrayReject = <T2 extends T1, T1>(fn: (value: T1) => value is T2) => (list: readonly T1[]) => Exclude<T1, T2>[];
I did not test them thoroughly though.
where is this type helper @vincaslt ?
Creating a ramda.d.ts
type definition file with the following:
```
declare namespace R {
interface Static {
filter
filter
}
}
````
worked for us. Allowing us to do:
export const getCount = <T extends any>(fn: (value: T) => boolean) =>
pipe(filter<T>(fn), length)
instead of the more verbose
export const getCount = <T extends any>(fn: (value: T) => boolean) =>
pipe<readonly T[], readonly T[], number>(filter<T>(fn), length)
Hey, I gave usage example in my comment above, but here's the actual definition: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/ramda/tools.d.ts#L217
This approach no longer works for 0.27.0. Filter
and reject
now appear to be defaulting to the FilterOnceApplied
signature which has no notions of readonly
collections. I'm not sure if this is the correct case to mention this one, but it seemed the most relevant
I tried to make simplest pipe function:
const takeLast10 = pipe(reverse, take(10))
And got this error:
It seems the issue is related to pretty much any function which can be used with more than one type, like reverse
can be used for both arrays and strings.
And filter can be used with objects and arrays.
It's not excuse for ramda I suppose, but it seems to be the reason.
Most helpful comment
This is also a big problem for us, even very simple things like:
do not compile:
The current typing for for
filter
is:For arrays the following typing is already fixing the issue:
This problem renders usage of ramda in TS pretty much unusable in some situations...