Repository: gcanti/fp-ts (ts)

Issue:
Add Array#fromPredicate and Array#fromOption
```ts
export function fromPredicate<A, B extends A>(refinement: Refinement<A, B>): (a: A) => Array<B>
export function fromPredicate<A>(predicate: Predicate<A>): (a: A) => Array<A>
export function fromPredicate<A>(predicate: Predicate<A>): (a: A) => Array<A> {
  return a => (predicate(a) ? [a] : [])
}
```

and

```ts
export function fromOption<A>(ma: Option<A>): Array<A> {
  return isSome(ma) ? [ma.value] : []
}
```

Pull request description:
Add fromPredicate & fromOption to Array
New feature for `Array`:
- added `fromPredicate`
- added `fromOption`

closes #1437 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcb204ef..191625a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,22 @@
 **Note**: A feature tagged as Experimental is in a
 high state of flux, you're at risk of it changing without notice.
 
+# 2.11
+
+- **New Feature**
+  - `Array`
+    - add `prependW`, `appendW` (@thewilkybarkid)
+  - `NonEmptyArray`
+    - add `matchLeft`, `matchRight`, `modifyHead`, `modifyLast` (@cdimitroulas)
+  - `ReadonlyArray`
+    - add `prependW`, `appendW` (@thewilkybarkid)
+  - `ReadonlyNonEmptyArray`
+    - add `matchLeft`, `matchRight`, `modifyHead`, `modifyLast` (@cdimitroulas)
+  - `TaskEither`
+    - add `fromTaskOption` (@thewilkybarkid)
+  - `TaskOption`
+    - add `fromTaskEither` (@thewilkybarkid)
+
 # 2.10.0-rc.5
 
 - **Bug Fix**
diff --git a/docs/modules/Array.ts.md b/docs/modules/Array.ts.md
index 0ec5b1cc..de9772e9 100644
--- a/docs/modules/Array.ts.md
+++ b/docs/modules/Array.ts.md
@@ -99,6 +99,8 @@ Added in v2.0.0
 - [constructors](#constructors)
   - [append](#append)
   - [appendW](#appendw)
+  - [fromOption](#fromoption)
+  - [fromPredicate](#frompredicate)
   - [makeBy](#makeby)
   - [prepend](#prepend)
   - [prependW](#prependw)
@@ -1354,6 +1356,27 @@ export declare const appendW: <A, B>(end: B) => (init: A[]) => NEA.NonEmptyArray
 
 Added in v2.11.0
 
+## fromOption
+
+**Signature**
+
+```ts
+export declare const fromOption: <A>(ma: O.Option<A>) => A[]
+```
+
+Added in v2.11.0
+
+## fromPredicate
+
+**Signature**
+
+```ts
+export declare function fromPredicate<A, B extends A>(refinement: Refinement<A, B>): (a: A) => Array<B>
+export declare function fromPredicate<A>(predicate: Predicate<A>): (a: A) => Array<A>
+```
+
+Added in v2.11.0
+
 ## makeBy
 
 Return a `Array` of length `n` with element `i` initialized with `f(i)`.
diff --git a/docs/modules/NonEmptyArray.ts.md b/docs/modules/NonEmptyArray.ts.md
index b216ebdd..b7f6819f 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
+
+Apply a function to the head, creating a new `NonEmptyArray`.
+
+**Signature**
+
+```ts
+export declare const modifyHead: <A>(f: Endomorphism<A>) => (as: NonEmptyArray<A>) => NonEmptyArray<A>
+```
+
+Added in v2.11.0
+
+## modifyLast
+
+Apply a function to the last element, creating a new `NonEmptyArray`.
+
+**Signature**
+
+```ts
+export declare const modifyLast: <A>(f: Endomorphism<A>) => (as: 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..8a46b73b 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 a `ReadonlyArray` 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 a `ReadonlyArray` 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
+
+Apply a function to the head, creating a new `ReadonlyNonEmptyArray`.
+
+**Signature**
+
+```ts
+export declare const modifyHead: <A>(f: Endomorphism<A>) => (as: ReadonlyNonEmptyArray<A>) => ReadonlyNonEmptyArray<A>
+```
+
+Added in v2.11.0
+
+## modifyLast
+
+Apply a function to the last element, creating a new `ReadonlyNonEmptyArray`.
+
+**Signature**
+
+```ts
+export declare const modifyLast: <A>(f: Endomorphism<A>) => (as: ReadonlyNonEmptyArray<A>) => ReadonlyNonEmptyArray<A>
+```
+
+Added in v2.11.0
+
 ## tail
 
 **Signature**
diff --git a/src/Array.ts b/src/Array.ts
index f95d490a..0cb8c95f 100644
--- a/src/Array.ts
+++ b/src/Array.ts
@@ -129,6 +129,24 @@ export const range = (start: number, end: number): Array<number> =>
  */
 export const replicate = <A>(n: number, a: A): Array<A> => makeBy(n, () => a)
 
+/**
+ * @category constructors
+ * @since 2.11.0
+ */
+export function fromPredicate<A, B extends A>(refinement: Refinement<A, B>): (a: A) => Array<B>
+export function fromPredicate<A>(predicate: Predicate<A>): (a: A) => Array<A>
+export function fromPredicate<A>(predicate: Predicate<A>): (a: A) => Array<A> {
+  return (a) => (predicate(a) ? [a] : [])
+}
+
+/**
+ * @category constructors
+ * @since 2.11.0
+ */
+export const fromOption = <A>(ma: Option<A>): Array<A> => {
+  return O.isSome(ma) ? [ma.value] : []
+}
+
 // -------------------------------------------------------------------------------------
 // destructors
 // -------------------------------------------------------------------------------------
diff --git a/src/NonEmptyArray.ts b/src/NonEmptyArray.ts
index e35c59a2..cc3d3208 100644
--- a/src/NonEmptyArray.ts
+++ b/src/NonEmptyArray.ts
@@ -21,7 +21,7 @@ import { Eq } from './Eq'
 import { Extend1 } from './Extend'
 import { Foldable1 } from './Foldable'
 import { FoldableWithIndex1 } from './FoldableWithIndex'
-import { identity, Lazy, pipe, Predicate, Refinement } from './function'
+import { Endomorphism, identity, Lazy, pipe, Predicate, Refinement } from './function'
 import { bindTo as bindTo_, flap as flap_, Functor1 } from './Functor'
 import { FunctorWithIndex1 } from './FunctorWithIndex'
 import { HKT } from './HKT'
@@ -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))
+
+/**
+ * Apply a function to the head, creating a new `NonEmptyArray`.
+ *
+ * @since 2.11.0
+ */
+export const modifyHead = <A>(f: Endomorphism<A>) => (as: NonEmptyArray<A>): NonEmptyArray<A> => [
+  f(head(as)),
+  ...tail(as)
+]
+
+/**
+ * Apply a function to the last element, creating a new `NonEmptyArray`.
+ *
+ * @since 2.11.0
+ */
+export const modifyLast = <A>(f: Endomorphism<A>) => (as: NonEmptyArray<A>): NonEmptyArray<A> =>
+  pipe(init(as), append(f(last(as))))
+
 // -------------------------------------------------------------------------------------
 // deprecated
 // -------------------------------------------------------------------------------------
diff --git a/src/ReadonlyNonEmptyArray.ts b/src/ReadonlyNonEmptyArray.ts
index e8159ba5..091cc9b5 100644
--- a/src/ReadonlyNonEmptyArray.ts
+++ b/src/ReadonlyNonEmptyArray.ts
@@ -21,7 +21,7 @@ import { Eq, fromEquals } from './Eq'
 import { Extend1 } from './Extend'
 import { Foldable1 } from './Foldable'
 import { FoldableWithIndex1 } from './FoldableWithIndex'
-import { identity, Lazy, pipe, Predicate, Refinement } from './function'
+import { Endomorphism, identity, Lazy, pipe, Predicate, Refinement } from './function'
 import { bindTo as bindTo_, flap as flap_, Functor1 } from './Functor'
 import { FunctorWithIndex1 } from './FunctorWithIndex'
 import { HKT } from './HKT'
@@ -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 a `ReadonlyArray` 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 a `ReadonlyArray` 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))
+
+/**
+ * Apply a function to the head, creating a new `ReadonlyNonEmptyArray`.
+ *
+ * @since 2.11.0
+ */
+export const modifyHead = <A>(f: Endomorphism<A>) => (as: ReadonlyNonEmptyArray<A>): ReadonlyNonEmptyArray<A> => [
+  f(head(as)),
+  ...tail(as)
+]
+
+/**
+ * Apply a function to the last element, creating a new `ReadonlyNonEmptyArray`.
+ *
+ * @since 2.11.0
+ */
+export const modifyLast = <A>(f: Endomorphism<A>) => (as: ReadonlyNonEmptyArray<A>): ReadonlyNonEmptyArray<A> =>
+  pipe(init(as), append(f(last(as))))
+
 // -------------------------------------------------------------------------------------
 // deprecated
 // -------------------------------------------------------------------------------------


Test patch:
diff --git a/test/Array.ts b/test/Array.ts
index 1f5a74f0..e4abf0c7 100644
--- a/test/Array.ts
+++ b/test/Array.ts
@@ -4,7 +4,7 @@ import * as _ from '../src/Array'
 import * as B from '../src/boolean'
 import * as E from '../src/Either'
 import * as Eq from '../src/Eq'
-import { identity, pipe, Predicate, tuple } from '../src/function'
+import { identity, pipe, Refinement, Predicate, tuple } from '../src/function'
 import * as M from '../src/Monoid'
 import * as N from '../src/number'
 import * as O from '../src/Option'
@@ -1115,4 +1115,23 @@ describe('Array', () => {
   it('copy', () => {
     U.deepStrictEqual(pipe([1, 2, 3], _.copy), [1, 2, 3])
   })
+
+  describe('fromPredicate', () => {
+    it('can create an array from a Refinement', () => {
+      const refinement: Refinement<unknown, string> = (a): a is string => typeof a === 'string'
+      U.deepStrictEqual(_.fromPredicate(refinement)('hello'), ['hello'])
+      U.deepStrictEqual(_.fromPredicate(refinement)(null), [])
+    })
+
+    it('can create an array from a Predicate', () => {
+      const predicate = (a: string) => a.length > 0
+      U.deepStrictEqual(_.fromPredicate(predicate)('hi'), ['hi'])
+      U.deepStrictEqual(_.fromPredicate(predicate)(''), [])
+    })
+  })
+
+  it('fromOption', () => {
+    U.deepStrictEqual(_.fromOption(O.some('hello')), ['hello'])
+    U.deepStrictEqual(_.fromOption(O.none), [])
+  })
 })
diff --git a/test/NonEmptyArray.ts b/test/NonEmptyArray.ts
index a9f58951..37eecb79 100644
--- a/test/NonEmptyArray.ts
+++ b/test/NonEmptyArray.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import { identity, pipe } from '../src/function'
+import { Endomorphism, identity, pipe } from '../src/function'
 import * as _ from '../src/NonEmptyArray'
 import * as N from '../src/number'
 import * as O from '../src/Option'
@@ -483,4 +483,38 @@ describe('NonEmptyArray', () => {
     // n out of bounds
     assertSingleChunk([1, 2], 3)
   })
+
+  it('matchLeft', () => {
+    U.deepStrictEqual(
+      pipe(
+        [1, 2, 3],
+        _.matchLeft((head, tail) => [head, tail])
+      ),
+      [1, [2, 3]]
+    )
+  })
+
+  it('matchRight', () => {
+    U.deepStrictEqual(
+      pipe(
+        [1, 2, 3],
+        _.matchRight((init, last) => [init, last])
+      ),
+      [[1, 2], 3]
+    )
+  })
+
+  it('modifyHead', () => {
+    const f: Endomorphism<string> = (s) => s + '!'
+    U.deepStrictEqual(pipe(['a'], _.modifyHead(f)), ['a!'])
+    U.deepStrictEqual(pipe(['a', 'b'], _.modifyHead(f)), ['a!', 'b'])
+    U.deepStrictEqual(pipe(['a', 'b', 'c'], _.modifyHead(f)), ['a!', 'b', 'c'])
+  })
+
+  it('modifyLast', () => {
+    const f: Endomorphism<string> = (s) => s + '!'
+    U.deepStrictEqual(pipe(['a'], _.modifyLast(f)), ['a!'])
+    U.deepStrictEqual(pipe(['a', 'b'], _.modifyLast(f)), ['a', 'b!'])
+    U.deepStrictEqual(pipe(['a', 'b', 'c'], _.modifyLast(f)), ['a', 'b', 'c!'])
+  })
 })
diff --git a/test/ReadonlyNonEmptyArray.ts b/test/ReadonlyNonEmptyArray.ts
index e810de35..77d9f286 100644
--- a/test/ReadonlyNonEmptyArray.ts
+++ b/test/ReadonlyNonEmptyArray.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert'
 import * as B from '../src/boolean'
 import * as Eq from '../src/Eq'
-import { identity, pipe } from '../src/function'
+import { Endomorphism, identity, pipe } from '../src/function'
 import * as N from '../src/number'
 import * as O from '../src/Option'
 import * as Ord from '../src/Ord'
@@ -620,4 +620,38 @@ describe('ReadonlyNonEmptyArray', () => {
     U.deepStrictEqual(_.makeBy(0, U.double), [0])
     U.deepStrictEqual(_.makeBy(-1, U.double), [0])
   })
+
+  it('matchLeft', () => {
+    U.deepStrictEqual(
+      pipe(
+        [1, 2, 3],
+        _.matchLeft((head, tail) => [head, tail])
+      ),
+      [1, [2, 3]]
+    )
+  })
+
+  it('matchRight', () => {
+    U.deepStrictEqual(
+      pipe(
+        [1, 2, 3],
+        _.matchRight((init, last) => [init, last])
+      ),
+      [[1, 2], 3]
+    )
+  })
+
+  it('modifyHead', () => {
+    const f: Endomorphism<string> = (s) => s + '!'
+    U.deepStrictEqual(pipe(['a'], _.modifyHead(f)), ['a!'])
+    U.deepStrictEqual(pipe(['a', 'b'], _.modifyHead(f)), ['a!', 'b'])
+    U.deepStrictEqual(pipe(['a', 'b', 'c'], _.modifyHead(f)), ['a!', 'b', 'c'])
+  })
+
+  it('modifyLast', () => {
+    const f: Endomorphism<string> = (s) => s + '!'
+    U.deepStrictEqual(pipe(['a'], _.modifyLast(f)), ['a!'])
+    U.deepStrictEqual(pipe(['a', 'b'], _.modifyLast(f)), ['a', 'b!'])
+    U.deepStrictEqual(pipe(['a', 'b', 'c'], _.modifyLast(f)), ['a', 'b', 'c!'])
+  })
 })