Skip to content

Commit

Permalink
Option: change firstSomeOf signature
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Feb 7, 2023
1 parent 03e0f6d commit 4463f4f
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-berries-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fp-ts/core": patch
---

Option: change `firstSomeOf` signature
2 changes: 1 addition & 1 deletion Option.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,6 @@ console.log(output); // Output: Error: Cannot parse 'Not a number' as a number
| `getOrUndefined` | `Option<A>` | `A \| undefined` |
| `orElse` | `Option<A>`, `Option<B>` | `Option<A \| B>` |
| `orElseEither` | `Option<A>`, `Option<B>` | `Option<Either<A, B>>` |
| `firstSomeOf` | `Option<A>`, `Iterable<Option<A>>` | `Option<A>` |
| `firstSomeOf` | `Iterable<Option<A>>` | `Option<A>` |
| `getFailureSemigroup` | `Semigroup<A>` | `Semigroup<Option<A>>` |
| `getFailureMonoid` | `Monoid<A>` | `Monoid<Option<A>>` |
53 changes: 28 additions & 25 deletions src/Option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { constNull, constUndefined, pipe } from "@fp-ts/core/Function"
import type { Kind, TypeLambda } from "@fp-ts/core/HKT"
import * as either from "@fp-ts/core/internal/Either"
import * as option from "@fp-ts/core/internal/Option"
import * as readonlyArray from "@fp-ts/core/internal/ReadonlyArray"
import * as N from "@fp-ts/core/Number"
import type { Predicate, Refinement } from "@fp-ts/core/Predicate"
import type * as alternative from "@fp-ts/core/typeclass/Alternative"
Expand Down Expand Up @@ -630,27 +629,18 @@ export const getFailureMonoid: <A>(M: Monoid<A>) => Monoid<Option<A>> = applicat
Applicative
)

/**
* Given an Iterable collection of `Option`s, the function returns the first `Some` option found in the collection.
*
* @param collection - An iterable collection of `Option` to be searched
*
* @category error handling
* @since 1.0.0
*/
export const firstSomeOf = <A>(collection: Iterable<Option<A>>) =>
(self: Option<A>): Option<A> => {
let out = self
const coproductMany = <A>(self: Option<A>, collection: Iterable<Option<A>>): Option<A> => {
let out = self
if (isSome(out)) {
return out
}
for (out of collection) {
if (isSome(out)) {
return out
}
for (out of collection) {
if (isSome(out)) {
return out
}
}
return out
}
return out
}

/**
* @category instances
Expand All @@ -659,7 +649,7 @@ export const firstSomeOf = <A>(collection: Iterable<Option<A>>) =>
export const SemiCoproduct: semiCoproduct.SemiCoproduct<OptionTypeLambda> = {
...Invariant,
coproduct: (self, that) => isSome(self) ? self : that,
coproductMany: (self, collection) => pipe(self, firstSomeOf(collection))
coproductMany
}

/**
Expand All @@ -678,19 +668,32 @@ export const coproductEither = <B>(that: Option<B>) =>
<A>(self: Option<A>): Option<Either<A, B>> =>
isNone(self) ? pipe(that, map(either.right)) : pipe(self, map(either.left))

/**
* Given an Iterable collection of `Option`s, the function returns the first `Some` option found in the collection.
*
* @param collection - An iterable collection of `Option` to be searched
*
* @category error handling
* @since 1.0.0
*/
export const firstSomeOf = <A>(collection: Iterable<Option<A>>): Option<A> => {
let out: Option<A> = none()
for (out of collection) {
if (isSome(out)) {
return out
}
}
return out
}

/**
* @category instances
* @since 1.0.0
*/
export const Coproduct: coproduct_.Coproduct<OptionTypeLambda> = {
...SemiCoproduct,
zero: none,
coproductAll: collection => {
const options = readonlyArray.fromIterable(collection)
return options.length > 0 ?
SemiCoproduct.coproductMany(options[0], options.slice(1)) :
option.none
}
coproductAll: firstSomeOf
}

/**
Expand Down
23 changes: 19 additions & 4 deletions test/Option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,30 @@ describe.concurrent("Option", () => {
Util.deepStrictEqual(pipe(_.some(1), _.coproductEither(_.some("a"))), _.some(E.left(1)))
})

it("coproductMany", () => {
const coproductMany = _.SemiCoproduct.coproductMany
Util.deepStrictEqual(coproductMany(_.some(1), []), _.some(1))
Util.deepStrictEqual(coproductMany(_.none(), []), _.none())
Util.deepStrictEqual(
coproductMany(_.none(), [_.none(), _.none(), _.none(), _.some(1)]),
_.some(1)
)
Util.deepStrictEqual(
coproductMany(_.none(), [_.none(), _.none(), _.none()]),
_.none()
)
})

it("firstSomeOf", () => {
Util.deepStrictEqual(pipe(_.some(1), _.firstSomeOf([])), _.some(1))
Util.deepStrictEqual(pipe(_.none(), _.firstSomeOf([])), _.none())
Util.deepStrictEqual(_.firstSomeOf([]), _.none())
Util.deepStrictEqual(_.firstSomeOf([_.some(1)]), _.some(1))
Util.deepStrictEqual(_.firstSomeOf([_.none()]), _.none())
Util.deepStrictEqual(
pipe(_.none(), _.firstSomeOf([_.none(), _.none(), _.none(), _.some(1)])),
_.firstSomeOf([_.none(), _.none(), _.none(), _.none(), _.some(1)]),
_.some(1)
)
Util.deepStrictEqual(
pipe(_.none(), _.firstSomeOf([_.none(), _.none(), _.none()])),
_.firstSomeOf([_.none(), _.none(), _.none(), _.none()]),
_.none()
)
})
Expand Down

0 comments on commit 4463f4f

Please sign in to comment.