-
Notifications
You must be signed in to change notification settings - Fork 127
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
feat(every): Add every
#631
base: master
Are you sure you want to change the base?
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 64ca4ef:
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #631 +/- ##
==========================================
+ Coverage 98.41% 98.62% +0.20%
==========================================
Files 127 155 +28
Lines 7954 11186 +3232
Branches 724 941 +217
==========================================
+ Hits 7828 11032 +3204
- Misses 123 151 +28
Partials 3 3 ☔ View full report in Codecov by Sentry. |
mapping.md
Outdated
@@ -109,6 +109,7 @@ documentation when migrating._ | |||
| [`unique`](https://remedajs.com/docs/#unique) | [`uniq`](https://lodash.com/docs/4.17.15#uniq) | [`uniq`](https://ramdajs.com/docs/#uniq) | | |||
| [`uniqueBy`](https://remedajs.com/docs/#uniqueBy) | [`uniqBy`](https://lodash.com/docs/4.17.15#uniqBy) | [`uniqBy`](https://ramdajs.com/docs/#uniqBy) | | |||
| [`uniqueWith`](https://remedajs.com/docs/#uniqueWith) | [`uniqWith`](https://lodash.com/docs/4.17.15#uniqWith) | [`uniqWith`](https://ramdajs.com/docs/#uniqWith) | | |||
| [`every`](https://remedajs.com/docs/#every) | [`every`](https://lodash.com/docs/4.17.15#every) | | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's sort this
src/every.ts
Outdated
* NOTE: If you want to use this inside `conditional` predicates and preserve proper type inference, | ||
* make sure to define the final type guard outside of the pipe. This is due to Typescript's limitation | ||
* in generic type inference. See examples below for details. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: what do you feel about dropping this? i think this is a problem with conditional
and not every
, and so notes like this should go into usage of conditional
(because they also apply to other predicates)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, i would say things like this should go in one of the conditional tests as like, a known issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(rather than the jsdoc, which appears every time we hover over the function)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed 👍
src/every.test.ts
Outdated
expect(data).toBe(true); | ||
}); | ||
|
||
test("narrows types when used with filter", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i wouldnt really think of this as a unit test... it tests filter
more than it does every
here, since we do already have a test of every
narrowing properly?
src/every.ts
Outdated
* R.every(['a', 0, 'b'], R.isString) // => false | ||
* @dataFirst | ||
* @indexed | ||
* @pipeable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: i think we only use @pipeable
for lazy functions, so we should remove this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(or at least, we should)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My first pr after releasing v2 would be to rename pipeable as lazy
oops sorry for the late review, was busy with my Day Job |
@cjquines no worries, same here. Pushed updates to your previous comments :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, will merge after todo
mapping.md
Outdated
| [`entries`](https://remedajs.com/docs/#entries) | | ||
| [`toPairs`](https://lodash.com/docs/4.17.15#toPairs) | [`toPairs`](https://ramdajs.com/docs/#toPairs) | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo: formatting here is now incorrect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if I see what's wrong here exactly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be a single line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah 🤦 ngl that took me a while to finally notice. Fixed, thanks! 🦅 👁️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scratch that i actually have some more questions
src/every.test.ts
Outdated
}); | ||
}); | ||
|
||
describe("data-last", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: rewrite these tests to use pipe
i think we mostly care about data-last usages in pipes, so i'd push for these tests to be in pipes
src/every.test.ts
Outdated
}); | ||
}); | ||
|
||
describe("in pipe", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: what does this test?
there's no inference being tested here
src/_types.ts
Outdated
export type TypePred<T, S extends T> = (input: T) => input is S; | ||
|
||
export type TypePredIndexed<T, S extends T> = ( | ||
input: T, | ||
index: number, | ||
array: ReadonlyArray<T>, | ||
) => input is S; | ||
|
||
export type TypePredIndexedOptional<T, S extends T> = ( | ||
input: T, | ||
index?: number, | ||
array?: ReadonlyArray<T>, | ||
) => input is S; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't define these here, all of these types are going away in v2. They aren't useful because the hide a very basic core concept of typing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where would be a better place to put them then, while in v1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
put the types inline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put them inline in the file, the reasoning behind putting them in _types
was that I immediately needed the same thing for some
in the next PR, but let's take it step by step
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eranhirsch lgty?
src/every.ts
Outdated
export namespace every { | ||
export function indexed<T, S extends T>( | ||
data: ReadonlyArray<T>, | ||
predicate: TypePredIndexed<T, S>, | ||
): data is Array<S>; | ||
export function indexed<T>( | ||
data: ReadonlyArray<T>, | ||
predicate: PredIndexed<T, boolean>, | ||
): boolean; | ||
export function indexed<T, S extends T>( | ||
predicate: TypePredIndexed<T, S>, | ||
): (data: ReadonlyArray<T>) => data is Array<S>; | ||
export function indexed<T>( | ||
predicate: PredIndexed<T, boolean>, | ||
): (data: ReadonlyArray<T>) => boolean; | ||
export function indexed(): unknown { | ||
return purry(_every(true), arguments); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the indexed version is not needed, make the regular version support the indexed version.
const data = every(isNumber)(input); | ||
expect(data).toBe(true); | ||
if (every(isNumber)(input)) { | ||
expectTypeOf(input).toEqualTypeOf<Array<number>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't mix type testing with runtime testing; they run at different times in the test cycle and thus bind two unrelated concerns.
That's why I prefer having a "runtime" block and a "typing" block.
src/every.ts
Outdated
* @example | ||
* // using `R.every` as a type predicate within `conditional` | ||
* const allNumbers = R.every(R.isNumber); | ||
* const result = R.pipe( | ||
* R.conditional( | ||
* [allNumbers, val => {...}] | ||
* // ^? number[] | ||
* ... | ||
* ) | ||
* ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if we support multiple example tags in our docs. This seems like a really big example using a utility that people might not know or understand; it makes the example more complex than it should be.
Our examples aren't usually full-blown usage examples; they just show a basic, almost trivial, use case so that people understand what the params look like and how they relate to the output.
src/every.ts
Outdated
return purry(_every(false), arguments); | ||
} | ||
|
||
const _every = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are dropping these underscore marker for private implementations. Use a full name for the function, everyImplementation
… case it gets resolved
Sorry, got caught up at work. I'll try to get to the other PRs during the weekend too. Tried addressing most of you guys' comments and posted some questions to others :) |
src/every.ts
Outdated
data: ReadonlyArray<T>, | ||
predicate: PredIndexed<T, boolean>, | ||
): boolean => | ||
data.every((element, index, array) => predicate(element, index, array)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out similar functions in V2, you don't need to create the arrow function here, it's redundant for our use-case
data.every((element, index, array) => predicate(element, index, array)); | |
data.every(predicate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did this, nut had to disable no-array-callback-reference
for it to be legal :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that rule is disabled altogether in v2
src/every.ts
Outdated
type TypePredIndexed<T, S extends T> = ( | ||
input: T, | ||
index: number, | ||
array: ReadonlyArray<T>, | ||
) => input is S; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline this, in v2 all functions have the function operand stated explicitly, these make it harder to read the function signature to understand what it accepts.
src/every.ts
Outdated
@@ -0,0 +1,63 @@ | |||
import type { PredIndexed } from "./_types"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, these are removed in v2, don't use them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the comments so late in the development. I've been working mainly on V2 so didn't have time to check other stuff. Because the release of v2 is happening very soon and it's likely we'll merge these changes only after releasing v2 it needs to match the style for v2.
]), | ||
); | ||
|
||
expect(resultBroken).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a runtime check in a typing test. Do we need this? it doesn't test anything that is related to the bug
]), | ||
); | ||
|
||
expect(resultCorrect).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly
test("returns correct values on simple arrays", () => { | ||
const trueInput = [1, 2, 3]; | ||
const trueData = every(trueInput, (val) => val < 5); | ||
expect(trueData).toBe(true); | ||
|
||
const falseInput = ["foo", "bar", "buzz"]; | ||
const falseData = every(falseInput, (val) => val.length > 3); | ||
expect(falseData).toBe(false); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep tests simple and short; it's better to have more tests than to have tests do more; that way, when a test fails, the actual failure is reported. And we can debug the specific case instead of having to comment out non-failing parts.
test("returns correct values on simple arrays", () => { | |
const trueInput = [1, 2, 3]; | |
const trueData = every(trueInput, (val) => val < 5); | |
expect(trueData).toBe(true); | |
const falseInput = ["foo", "bar", "buzz"]; | |
const falseData = every(falseInput, (val) => val.length > 3); | |
expect(falseData).toBe(false); | |
}); | |
test("all values meet the criteria", () => { | |
expect(every([1, 2, 3], (val) => val < 5)).toBe(true); | |
}); | |
test("a value doesn't meet the criteria", () => { | |
expect(every(["foo", "bar", "buzz"], (val) => val.length > 3)).toBe(false); | |
}); |
expect(falseData).toBe(false); | ||
}); | ||
|
||
// empty set check |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test name describes it, we don't need documentation inside tests
// empty set check |
const input = [1, 2, 3]; | ||
const data = every(input, (_, index) => index < 5); | ||
expect(data).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏼 nice, this is an advantage of moving indexed predicates to the main signature, we test them like regular logic and not disconnected from other concerns!
const dataPipe = pipe( | ||
input, | ||
every((val, index, arr) => { | ||
expectTypeOf(val).toEqualTypeOf<number>(); | ||
expectTypeOf(index).toEqualTypeOf<number>(); | ||
expectTypeOf(arr).toEqualTypeOf<ReadonlyArray<number>>(); | ||
return index < 5; | ||
}), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's actually a lot of cases where the piped dataLast version breaking without the dataFirst version breaking, let's split these tests
expectTypeOf(index).toEqualTypeOf<number>(); | ||
expectTypeOf(arr).toEqualTypeOf<ReadonlyArray<number>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to test these, it's part of the function signature, and they aren't inferred or have any "magic" that might break them.
expectTypeOf(index).toEqualTypeOf<number>(); | |
expectTypeOf(arr).toEqualTypeOf<ReadonlyArray<number>>(); |
return true; | ||
}), | ||
); | ||
expect(data).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
runtime concern in a type test
export function every<T, S extends T>( | ||
data: ReadonlyArray<T>, | ||
predicate: (input: T, index: number, array: ReadonlyArray<T>) => input is S, | ||
): data is Array<S>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The typing does not respect the shape of the input in the output. We can take the tuple as a generic parameter and use it to return a result that is the same shape.
export function every<T, S extends T>( | |
data: ReadonlyArray<T>, | |
predicate: (input: T, index: number, array: ReadonlyArray<T>) => input is S, | |
): data is Array<S>; | |
export function every<T extends IterableContainer, S extends T[number]>( | |
data: T, | |
predicate: (input: T[number], index: number, array: T) => input is S, | |
): data is { [K in keyof T]: S }; |
This would allow someone to test something like [string | number, ...Array<string | number>]
and get back [string, ...Array<string>]
The non-narrowing overload doesn't need this treatment because it returns a boolean
export function every<T, S extends T>( | ||
predicate: (input: T, index: number, array: ReadonlyArray<T>) => input is S, | ||
): (data: ReadonlyArray<T>) => data is Array<S>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
export function every<T, S extends T>( | |
predicate: (input: T, index: number, array: ReadonlyArray<T>) => input is S, | |
): (data: ReadonlyArray<T>) => data is Array<S>; | |
export function every<T extends IterableContainer, S extends T[number]>( | |
predicate: (input: T[number], index: number, array: T) => input is S, | |
): (data: T) => data is { [K in keyof T]: S }; |
No worries. How soon are we talking? If preferred, I can wait for |
It should happen in the next 2 weeks |
Following up on conversation in #624, I started with implementing
every
.Make sure that you:
src/index.ts
mapping.md
We use semantic PR titles to automate the release process!
https://conventionalcommits.org
PRs should be titled following using the format:
< TYPE >(< scope >)?: description
Available Types:
feat
: new functions, and changes to a function's type that would impact users.fix
: changes to the runtime behavior of an existing function, or refinements to it's type that shouldn't impact most users.perf
: changes to function implementations that improve a functions runtime performance.refactor
: changes to function implementations that are neitherfix
norperf
test
: tests-only changes (transparent to users of the function).docs
: changes to the documentation of a function or the documentation site.build
,ci
,style
,chore
, andrevert
: are only relevant for the internals of the library.For scope put the name of the function you are working on (either new or
existing).