Repository: gcanti/fp-ts
Pull request #1449 (2021-03-27 17:37:39)

Non empty arr additions
**New Feature** (for v2.11)

NonEmptyArray/ReadonlyNonEmptyArray:
- added `matchLeft`
- added `matchRight`
- added `modifyHead`
- added `modifyLast`

These were added based on the discussion seen in https://github.com/gcanti/fp-ts/issues/1420




Patch:
diff --git a/docs/modules/NonEmptyArray.ts.md b/docs/modules/NonEmptyArray.ts.md
index b216ebdd..25c6dcaf 100644
--- a/docs/modules/NonEmptyArray.ts.md
+++ b/docs/modules/NonEmptyArray.ts.md
@@ -81,6 +81,8 @@ Added in v2.0.0
   - [~~cons~~](#cons)
   - [~~snoc~~](#snoc)
 - [destructors](#destructors)
+  - [matchLeft](#matchleft)
+  - [matchRight](#matchright)
   - [unappend](#unappend)
   - [unprepend](#unprepend)
   - [~~uncons~~](#uncons)
@@ -119,6 +121,8 @@ Added in v2.0.0
   - [last](#last)
   - [max](#max)
   - [min](#min)
+  - [modifyHead](#modifyhead)
+  - [modifyLast](#modifylast)
   - [sequence](#sequence)
   - [tail](#tail)
   - [traverse](#traverse)
@@ -738,6 +742,30 @@ Added in v2.0.0
 
 # destructors
 
+## matchLeft
+
+Break an array into its first element and remaining elements
+
+**Signature**
+
+```ts
+export declare const matchLeft: <A, B>(f: (head: A, tail: A[]) => B) => (as: NonEmptyArray<A>) => B
+```
+
+Added in v2.11.0
+
+## matchRight
+
+Break an array into its initial elements and the last element
+
+**Signature**
+
+```ts
+export declare const matchRight: <A, B>(f: (init: A[], last: A) => B) => (as: NonEmptyArray<A>) => B
+```
+
+Added in v2.11.0
+
 ## unappend
 
 Return the tuple of the `init` and the `last`.
@@ -1158,6 +1186,30 @@ export declare const min: <A>(ord: Ord<A>) => (nea: NonEmptyArray<A>) => A
 
 Added in v2.0.0
 
+## modifyHead
+
+Modifies the first element of the array
+
+**Signature**
+
+```ts
+export declare const modifyHead: <A>(f: (a: A) => A) => (nea: NonEmptyArray<A>) => NonEmptyArray<A>
+```
+
+Added in v2.11.0
+
+## modifyLast
+
+Modifies the last element of the array
+
+**Signature**
+
+```ts
+export declare const modifyLast: <A>(f: (a: A) => A) => (nea: NonEmptyArray<A>) => NonEmptyArray<A>
+```
+
+Added in v2.11.0
+
 ## sequence
 
 **Signature**
diff --git a/docs/modules/ReadonlyNonEmptyArray.ts.md b/docs/modules/ReadonlyNonEmptyArray.ts.md
index 23c5ac5d..db31266f 100644
--- a/docs/modules/ReadonlyNonEmptyArray.ts.md
+++ b/docs/modules/ReadonlyNonEmptyArray.ts.md
@@ -86,6 +86,8 @@ Added in v2.5.0
   - [~~cons~~](#cons)
   - [~~snoc~~](#snoc)
 - [destructors](#destructors)
+  - [matchLeft](#matchleft)
+  - [matchRight](#matchright)
   - [unappend](#unappend)
   - [unprepend](#unprepend)
   - [~~uncons~~](#uncons)
@@ -125,6 +127,8 @@ Added in v2.5.0
   - [last](#last)
   - [max](#max)
   - [min](#min)
+  - [modifyHead](#modifyhead)
+  - [modifyLast](#modifylast)
   - [tail](#tail)
   - [~~fold~~](#fold)
 
@@ -825,6 +829,30 @@ Added in v2.5.0
 
 # destructors
 
+## matchLeft
+
+Break an array into its first element and remaining elements
+
+**Signature**
+
+```ts
+export declare const matchLeft: <A, B>(f: (head: A, tail: readonly A[]) => B) => (as: ReadonlyNonEmptyArray<A>) => B
+```
+
+Added in v2.11.0
+
+## matchRight
+
+Break an array into its initial elements and the last element
+
+**Signature**
+
+```ts
+export declare const matchRight: <A, B>(f: (init: readonly A[], last: A) => B) => (as: ReadonlyNonEmptyArray<A>) => B
+```
+
+Added in v2.11.0
+
 ## unappend
 
 Return the tuple of the `init` and the `last`.
@@ -1246,6 +1274,30 @@ export declare const min: <A>(O: Ord<A>) => (as: ReadonlyNonEmptyArray<A>) => A
 
 Added in v2.5.0
 
+## modifyHead
+
+Modifies the first element of the array
+
+**Signature**
+
+```ts
+export declare const modifyHead: <A>(f: (a: A) => A) => (nea: ReadonlyNonEmptyArray<A>) => ReadonlyNonEmptyArray<A>
+```
+
+Added in v2.11.0
+
+## modifyLast
+
+Modifies the last element of the array
+
+**Signature**
+
+```ts
+export declare const modifyLast: <A>(f: (a: A) => A) => (nea: ReadonlyNonEmptyArray<A>) => ReadonlyNonEmptyArray<A>
+```
+
+Added in v2.11.0
+
 ## tail
 
 **Signature**
diff --git a/src/NonEmptyArray.ts b/src/NonEmptyArray.ts
index e35c59a2..5874dcb2 100644
--- a/src/NonEmptyArray.ts
+++ b/src/NonEmptyArray.ts
@@ -1064,6 +1064,41 @@ export const max: <A>(ord: Ord<A>) => (nea: NonEmptyArray<A>) => A = RNEA.max
  */
 export const concatAll = <A>(S: Semigroup<A>) => (as: NonEmptyArray<A>): A => as.reduce(S.concat)
 
+/**
+ * Break an array into its first element and remaining elements
+ *
+ * @category destructors
+ * @since 2.11.0
+ */
+export const matchLeft = <A, B>(f: (head: A, tail: Array<A>) => B) => (as: NonEmptyArray<A>): B => f(head(as), tail(as))
+
+/**
+ * Break an array into its initial elements and the last element
+ *
+ * @category destructors
+ * @since 2.11.0
+ */
+export const matchRight = <A, B>(f: (init: Array<A>, last: A) => B) => (as: NonEmptyArray<A>): B =>
+  f(init(as), last(as))
+
+/**
+ * Modifies the first element of the array
+ *
+ * @since 2.11.0
+ */
+export const modifyHead = <A>(f: (a: A) => A): ((nea: NonEmptyArray<A>) => NonEmptyArray<A>) => {
+  return matchLeft((head, tail) => pipe(tail, prepend(f(head))))
+}
+
+/**
+ * Modifies the last element of the array
+ *
+ * @since 2.11.0
+ */
+export const modifyLast = <A>(f: (a: A) => A): ((nea: NonEmptyArray<A>) => NonEmptyArray<A>) => {
+  return matchRight((init, last) => pipe(init, append(f(last))))
+}
+
 // -------------------------------------------------------------------------------------
 // deprecated
 // -------------------------------------------------------------------------------------
diff --git a/src/ReadonlyNonEmptyArray.ts b/src/ReadonlyNonEmptyArray.ts
index e8159ba5..dbad7eea 100644
--- a/src/ReadonlyNonEmptyArray.ts
+++ b/src/ReadonlyNonEmptyArray.ts
@@ -1116,6 +1116,42 @@ export const max = <A>(O: Ord<A>): ((as: ReadonlyNonEmptyArray<A>) => A) => {
  */
 export const concatAll = <A>(S: Semigroup<A>) => (as: ReadonlyNonEmptyArray<A>): A => as.reduce(S.concat)
 
+/**
+ * Break an array into its first element and remaining elements
+ *
+ * @category destructors
+ * @since 2.11.0
+ */
+export const matchLeft = <A, B>(f: (head: A, tail: ReadonlyArray<A>) => B) => (as: ReadonlyNonEmptyArray<A>): B =>
+  f(head(as), tail(as))
+
+/**
+ * Break an array into its initial elements and the last element
+ *
+ * @category destructors
+ * @since 2.11.0
+ */
+export const matchRight = <A, B>(f: (init: ReadonlyArray<A>, last: A) => B) => (as: ReadonlyNonEmptyArray<A>): B =>
+  f(init(as), last(as))
+
+/**
+ * Modifies the first element of the array
+ *
+ * @since 2.11.0
+ */
+export const modifyHead = <A>(f: (a: A) => A): ((nea: ReadonlyNonEmptyArray<A>) => ReadonlyNonEmptyArray<A>) => {
+  return matchLeft((head, tail) => pipe(tail, prepend(f(head))))
+}
+
+/**
+ * Modifies the last element of the array
+ *
+ * @since 2.11.0
+ */
+export const modifyLast = <A>(f: (a: A) => A): ((nea: ReadonlyNonEmptyArray<A>) => ReadonlyNonEmptyArray<A>) => {
+  return matchRight((init, last) => pipe(init, append(f(last))))
+}
+
 // -------------------------------------------------------------------------------------
 // deprecated
 // -------------------------------------------------------------------------------------


Test patch:
diff --git a/test/NonEmptyArray.ts b/test/NonEmptyArray.ts
index a9f58951..63da4cfa 100644
--- a/test/NonEmptyArray.ts
+++ b/test/NonEmptyArray.ts
@@ -483,4 +483,20 @@ describe('NonEmptyArray', () => {
     // n out of bounds
     assertSingleChunk([1, 2], 3)
   })
+
+  it('matchLeft', () => {
+    U.deepStrictEqual(_.matchLeft((head, tail) => [head, tail])([1, 2, 3]), [1, [2, 3]])
+  })
+
+  it('matchRight', () => {
+    U.deepStrictEqual(_.matchRight((init, last) => [init, last])([1, 2, 3]), [[1, 2], 3])
+  })
+
+  it('modifyHead', () => {
+    U.deepStrictEqual(_.modifyHead((x: number) => x + 10)([1, 2, 3]), [11, 2, 3])
+  })
+
+  it('modifyLast', () => {
+    U.deepStrictEqual(_.modifyLast((x: number) => x + 10)([1, 2, 3]), [1, 2, 13])
+  })
 })
diff --git a/test/ReadonlyNonEmptyArray.ts b/test/ReadonlyNonEmptyArray.ts
index e810de35..1338135b 100644
--- a/test/ReadonlyNonEmptyArray.ts
+++ b/test/ReadonlyNonEmptyArray.ts
@@ -620,4 +620,20 @@ describe('ReadonlyNonEmptyArray', () => {
     U.deepStrictEqual(_.makeBy(0, U.double), [0])
     U.deepStrictEqual(_.makeBy(-1, U.double), [0])
   })
+
+  it('matchLeft', () => {
+    U.deepStrictEqual(_.matchLeft((head, tail) => [head, tail])([1, 2, 3]), [1, [2, 3]])
+  })
+
+  it('matchRight', () => {
+    U.deepStrictEqual(_.matchRight((init, last) => [init, last])([1, 2, 3]), [[1, 2], 3])
+  })
+
+  it('modifyHead', () => {
+    U.deepStrictEqual(_.modifyHead((x: number) => x + 10)([1, 2, 3]), [11, 2, 3])
+  })
+
+  it('modifyLast', () => {
+    U.deepStrictEqual(_.modifyLast((x: number) => x + 10)([1, 2, 3]), [1, 2, 13])
+  })
 })