Skip to content

Commit

Permalink
Add Either.liftPredicate (#2919)
Browse files Browse the repository at this point in the history
  • Loading branch information
LaureRC authored and mikearnaldi committed Jun 10, 2024
1 parent f085f92 commit 98ecc5b
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/two-bananas-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

Add Either.liftPredicate
74 changes: 74 additions & 0 deletions packages/effect/dtslint/Either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,80 @@ string$string.pipe(Either.andThen(string$number))
// $ExpectType Either<number, string>
string$string.pipe(Either.andThen(() => string$number))

// -------------------------------------------------------------------------------------
// liftPredicate
// -------------------------------------------------------------------------------------

declare const primitiveNumber: number
declare const primitiveNumberOrString: string | number
declare const predicateNumbersOrStrings: Predicate.Predicate<number | string>

// $ExpectType Either<string, "b">
pipe(
primitiveNumberOrString,
Either.liftPredicate(Predicate.isString, (
_s // $ExpectType string | number
) => "b" as const)
)

// $ExpectType Either<string, "b">
Either.liftPredicate(primitiveNumberOrString, Predicate.isString, (
_s // $ExpectType string | number
) => "b" as const)

// $ExpectType Either<number, "b">
pipe(
primitiveNumberOrString,
Either.liftPredicate(
(
n // $ExpectType string | number
): n is number => typeof n === "number",
(
_s // $ExpectType string | number
) => "b" as const
)
)

// $ExpectType Either<number, "b">
Either.liftPredicate(
primitiveNumberOrString,
(
n // $ExpectType string | number
): n is number => typeof n === "number",
(
_s // $ExpectType string | number
) => "b" as const
)

// $ExpectType Either<string | number, "b">
pipe(
primitiveNumberOrString,
Either.liftPredicate(predicateNumbersOrStrings, (
_s // $ExpectType string | number
) => "b" as const)
)

// $ExpectType Either<number, "b">
pipe(
primitiveNumber,
Either.liftPredicate(predicateNumbersOrStrings, (
_s // $ExpectType number
) => "b" as const)
)

// $ExpectType Either<number, "b">
pipe(
primitiveNumber,
Either.liftPredicate(
(
_n // $ExpectType number
) => true,
(
_s // $ExpectType number
) => "b" as const
)
)

// -------------------------------------------------------------------------------------
// filterOrLeft
// -------------------------------------------------------------------------------------
Expand Down
51 changes: 51 additions & 0 deletions packages/effect/src/Either.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,57 @@ export const match: {
}): B | C => isLeft(self) ? onLeft(self.left) : onRight(self.right)
)

/**
* Transforms a `Predicate` function into a `Right` of the input value if the predicate returns `true`
* or `Left` of the result of the provided function if the predicate returns false
*
* @param predicate - A `Predicate` function that takes in a value of type `A` and returns a boolean.
*
* @example
* import { pipe, Either } from "effect"
*
* const isPositive = (n: number): boolean => n > 0
*
* assert.deepStrictEqual(
* pipe(
* 1,
* Either.liftPredicate(isPositive, n => `${n} is not positive`)
* ),
* Either.right(1)
* )
* assert.deepStrictEqual(
* pipe(
* 0,
* Either.liftPredicate(isPositive, n => `${n} is not positive`)
* ),
* Either.left("0 is not positive")
* )
*
* @category lifting
* @since 3.4.0
*/
export const liftPredicate: {
<A, B extends A, E>(refinement: Refinement<NoInfer<A>, B>, orLeftWith: (a: NoInfer<A>) => E): (a: A) => Either<B, E>
<A, E>(
predicate: Predicate<NoInfer<A>>,
orLeftWith: (a: NoInfer<A>) => E
): (a: A) => Either<A, E>
<A, E, B extends A>(
self: A,
refinement: Refinement<A, B>,
orLeftWith: (a: A) => E
): Either<B, E>
<A, E>(
self: A,
predicate: Predicate<NoInfer<A>>,
orLeftWith: (a: NoInfer<A>) => E
): Either<A, E>
} = dual(
3,
<A, E>(a: A, predicate: Predicate<A>, orLeftWith: (a: A) => E): Either<A, E> =>
predicate(a) ? right(a) : left(orLeftWith(a))
)

/**
* Filter the right value with the provided function.
* If the predicate fails, set the left value with the result of the provided function.
Expand Down
41 changes: 41 additions & 0 deletions packages/effect/test/Either.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,47 @@ describe("Either", () => {
Util.deepStrictEqual(Either.flip(Either.left("b")), Either.right("b"))
})

it("liftPredicate", () => {
const isPositivePredicate = (n: number) => n > 0
const onPositivePredicateError = (n: number) => `${n} is not positive`
const isNumberRefinement = (n: string | number): n is number => typeof n === "number"
const onNumberRefinementError = (n: string | number) => `${n} is not a number`

Util.deepStrictEqual(
pipe(1, Either.liftPredicate(isPositivePredicate, onPositivePredicateError)),
Either.right(1)
)
Util.deepStrictEqual(
pipe(-1, Either.liftPredicate(isPositivePredicate, onPositivePredicateError)),
Either.left(`-1 is not positive`)
)
Util.deepStrictEqual(
pipe(1, Either.liftPredicate(isNumberRefinement, onNumberRefinementError)),
Either.right(1)
)
Util.deepStrictEqual(
pipe("string", Either.liftPredicate(isNumberRefinement, onNumberRefinementError)),
Either.left(`string is not a number`)
)

Util.deepStrictEqual(
Either.liftPredicate(1, isPositivePredicate, onPositivePredicateError),
Either.right(1)
)
Util.deepStrictEqual(
Either.liftPredicate(-1, isPositivePredicate, onPositivePredicateError),
Either.left(`-1 is not positive`)
)
Util.deepStrictEqual(
Either.liftPredicate(1, isNumberRefinement, onNumberRefinementError),
Either.right(1)
)
Util.deepStrictEqual(
Either.liftPredicate("string", isNumberRefinement, onNumberRefinementError),
Either.left(`string is not a number`)
)
})

it("filterOrLeft", () => {
Util.deepStrictEqual(Either.filterOrLeft(Either.right(1), (n) => n > 0, () => "a"), Either.right(1))
Util.deepStrictEqual(Either.filterOrLeft(Either.right(1), (n) => n > 1, () => "a"), Either.left("a"))
Expand Down

0 comments on commit 98ecc5b

Please sign in to comment.