Open Clindbergh opened 8 months ago
I realize there would potentially have to be two functions: One to zoom into all elements within that array (focusing on a subset of the array) and one that would only look at the first Element.
I suggest an implementation along these lines for the latter:
const firstIndexBy: {
<S extends A, B extends A, A = S>(
refinement: Refinement<A, B>,
message?: string
): Optional<ReadonlyArray<A>, A>
<S extends A, A = S>(predicate: Predicate<A>, message?: string): Optional<ReadonlyArray<A>, A>
} = <S>(predicate: Predicate<S>, message?: string): Optional<ReadonlyArray<S>, S> =>
optional(
(s) =>
pipe(
s,
ReadonlyArray.findFirstIndex(predicate),
Option.flatMap(i => ReadonlyArray.get(s, i)),
Either.fromOption(() => new Error(message ?? `No index matching predicate`))
),
(a) =>
(s) =>
pipe(
s,
ReadonlyArray.findFirstIndex(predicate),
Option.flatMap(i => ReadonlyArray.replaceOption(i, a)(s)),
Either.fromOption(() => new Error(`No index matching predicate`))
)
)
Would you be open for PRs?
findFirst
I realize a function such as the one I suggested is already available.
https://github.com/fp-ts/optic/blob/55ffb9e8b5e01c8483795adb19082fba6ef2a052/src/index.ts#L657
findAll
or elementsBy
I suggest a similar function to access a subset of an array.
An example implementation
/**
* Returns a Lens to a subset of array elements satisfying a predicate
*
* If the same number of elements are passed to the setter, they are applied to the selected elements.
* If an empty array is passed to the setter, the elements remain unchanged
* If less than the number of elements are passed to the setter, the new elements are cycled
* If more than the number of elements are passed to the setter, only the first number of elements satisfying the predicate
* are used
*
* @example
*
* const numbers: ReadonlyArray<number> = makeBy(10, x => x)
* const evenNumbers: Lens<ReadonlyArray<number>, ReadonlyArray<number>> = elementsBy<number, number>(number => number % 2 === 0);
*
* console.log(numbers)
* // [
* // 0, 1, 2, 3, 4,
* // 5, 6, 7, 8, 9
* // ]
* console.log(get(evenNumbers)(numbers))
* // [ 0, 2, 4, 6, 8 ]
* console.log(modify(evenNumbers)(map(x => x*2))(numbers))
* // [
* // 0, 1, 4, 3, 8,
* // 5, 12, 7, 16, 9
* // ]
* console.log(replace(evenNumbers)([-1])(numbers))
* // [
* // -1, 1, -1, 3, -1,
* // 5, -1, 7, -1, 9
* // ]
*
*
* @param predicate The predicate to select the elements
*/
export const elementsBy: {
<S extends A, B extends A, A = S>(
refinement: Refinement<A, B>,
): Optional<ReadonlyArray<A>, A>;
<S extends A, A = S>(predicate: Predicate<A>): Lens<
ReadonlyArray<A>,
ReadonlyArray<A>
>;
} = <S>(predicate: Predicate<S>): Lens<ReadonlyArray<S>, ReadonlyArray<S>> =>
lens(
(a): ReadonlyArray<S> => filter(predicate)(a),
(s) => (a) =>
pipe(
a,
reduce(
{ as: empty(), si: 0 },
({ as, si }: { as: ReadonlyArray<S>; si: number }, a, i) =>
predicate(a)
? { as: as.concat(s.at(si % s.length) ?? a), si: si + 1 }
: { as: as.concat(a), si },
),
(x) => x.as,
),
);
š Feature request
Current Behavior
Currently a specific number is needed to modify an array element at a specific index.
In most cases I must find the desired index beforehand, e.g. by iterating on all elements within the array and find the required id.
Desired Behavior
Create a lens on all elements within the array satisfying the specific predicates, similar to
filter
.Example with filter for keys:
Desired behaviour for index elements.
Suggested Solution
Implement a new method
filterIndex
similar tofilter
that returns anOptional
.Who does this impact? Who is this for?
Anyone who uses arrays.
Describe alternatives you've considered
Alternatively a lens factory method may be created, but this is not as clean as it could be.
Your environment