Skip to content

Commit a1213ff

Browse files
tim-smarteffect-bot
authored andcommitted
add Effect.filterEffect* apis (#4335)
1 parent 5b9fa16 commit a1213ff

File tree

4 files changed

+253
-0
lines changed

4 files changed

+253
-0
lines changed

.changeset/warm-clouds-grab.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
"effect": minor
3+
---
4+
5+
add Effect.filterEffect\* apis
6+
7+
#### Effect.filterEffectOrElse
8+
9+
Filters an effect with an effectful predicate, falling back to an alternative
10+
effect if the predicate fails.
11+
12+
```ts
13+
import { Effect, pipe } from "effect"
14+
15+
// Define a user interface
16+
interface User {
17+
readonly name: string
18+
}
19+
20+
// Simulate an asynchronous authentication function
21+
declare const auth: () => Promise<User | null>
22+
23+
const program = pipe(
24+
Effect.promise(() => auth()),
25+
// Use filterEffectOrElse with an effectful predicate
26+
Effect.filterEffectOrElse({
27+
predicate: (user) => Effect.succeed(user !== null),
28+
orElse: (user) => Effect.fail(new Error(`Unauthorized user: ${user}`))
29+
})
30+
)
31+
```
32+
33+
#### Effect.filterEffectOrFail
34+
35+
Filters an effect with an effectful predicate, failing with a custom error if the predicate fails.
36+
37+
```ts
38+
import { Effect, pipe } from "effect"
39+
40+
// Define a user interface
41+
interface User {
42+
readonly name: string
43+
}
44+
45+
// Simulate an asynchronous authentication function
46+
declare const auth: () => Promise<User | null>
47+
48+
const program = pipe(
49+
Effect.promise(() => auth()),
50+
// Use filterEffectOrFail with an effectful predicate
51+
Effect.filterEffectOrFail({
52+
predicate: (user) => Effect.succeed(user !== null),
53+
orFailWith: (user) => Effect.fail(new Error(`Unauthorized user: ${user}`))
54+
})
55+
)
56+
```

packages/effect/src/Effect.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8315,6 +8315,113 @@ export const filterOrFail: {
83158315
<A, E, R>(self: Effect<A, E, R>, predicate: Predicate<A>): Effect<A, E | Cause.NoSuchElementException, R>
83168316
} = effect.filterOrFail
83178317

8318+
/**
8319+
* Filters an effect with an effectful predicate, falling back to an alternative
8320+
* effect if the predicate fails.
8321+
*
8322+
* **Details**
8323+
*
8324+
* This function applies a predicate to the result of an effect. If the
8325+
* predicate evaluates to `false`, the effect falls back to the `orElse`
8326+
* effect. The `orElse` effect can produce an alternative value or perform
8327+
* additional computations.
8328+
*
8329+
* @example
8330+
* ```ts
8331+
* import { Effect, pipe } from "effect"
8332+
*
8333+
* // Define a user interface
8334+
* interface User {
8335+
* readonly name: string
8336+
* }
8337+
*
8338+
* // Simulate an asynchronous authentication function
8339+
* declare const auth: () => Promise<User | null>
8340+
*
8341+
* const program = pipe(
8342+
* Effect.promise(() => auth()),
8343+
* // Use filterEffectOrElse with an effectful predicate
8344+
* Effect.filterEffectOrElse({
8345+
* predicate: (user) => Effect.succeed(user !== null),
8346+
* orElse: (user) => Effect.fail(new Error(`Unauthorized user: ${user}`))
8347+
* }),
8348+
* )
8349+
* ```
8350+
*
8351+
* @since 3.13.0
8352+
* @category Filtering
8353+
*/
8354+
export const filterEffectOrElse: {
8355+
<A, E2, R2, A2, E3, R3>(
8356+
options: {
8357+
readonly predicate: (a: NoInfer<A>) => Effect<boolean, E2, R2>
8358+
readonly orElse: (a: NoInfer<A>) => Effect<A2, E3, R3>
8359+
}
8360+
): <E, R>(self: Effect<A, E, R>) => Effect<A | A2, E | E2 | E3, R | R2 | R3>
8361+
<A, E, R, E2, R2, A2, E3, R3>(
8362+
self: Effect<A, E, R>,
8363+
options: {
8364+
readonly predicate: (a: A) => Effect<boolean, E2, R2>
8365+
readonly orElse: (a: A) => Effect<A2, E3, R3>
8366+
}
8367+
): Effect<A | A2, E | E2 | E3, R | R2 | R3>
8368+
} = core.filterEffectOrElse
8369+
8370+
/**
8371+
* Filters an effect with an effectful predicate, failing with a custom error if the predicate fails.
8372+
*
8373+
* **Details**
8374+
*
8375+
* This function applies a predicate to the result of an effect. If the
8376+
* predicate evaluates to `false`, the effect fails with a custom error
8377+
* generated by the `orFailWith` function.
8378+
*
8379+
* **When to Use**
8380+
*
8381+
* This is useful for enforcing constraints and treating violations as
8382+
* recoverable errors.
8383+
*
8384+
* @example
8385+
* ```ts
8386+
* import { Effect, pipe } from "effect"
8387+
*
8388+
* // Define a user interface
8389+
* interface User {
8390+
* readonly name: string
8391+
* }
8392+
*
8393+
* // Simulate an asynchronous authentication function
8394+
* declare const auth: () => Promise<User | null>
8395+
*
8396+
* const program = pipe(
8397+
* Effect.promise(() => auth()),
8398+
* // Use filterEffectOrFail with an effectful predicate
8399+
* Effect.filterEffectOrFail({
8400+
* predicate: (user) => Effect.succeed(user !== null),
8401+
* orFailWith: () => new Error("Unauthorized")
8402+
* }),
8403+
* )
8404+
* ```
8405+
*
8406+
* @since 3.13.0
8407+
* @category Filtering
8408+
*/
8409+
export const filterEffectOrFail: {
8410+
<A, E2, R2, E3>(
8411+
options: {
8412+
readonly predicate: (a: NoInfer<A>) => Effect<boolean, E2, R2>
8413+
readonly orFailWith: (a: NoInfer<A>) => E3
8414+
}
8415+
): <E, R>(self: Effect<A, E, R>) => Effect<A, E | E2 | E3, R | R2>
8416+
<A, E, R, E2, R2, E3>(
8417+
self: Effect<A, E, R>,
8418+
options: {
8419+
readonly predicate: (a: A) => Effect<boolean, E2, R2>
8420+
readonly orFailWith: (a: A) => E3
8421+
}
8422+
): Effect<A, E | E2 | E3, R | R2>
8423+
} = core.filterEffectOrFail
8424+
83188425
/**
83198426
* Executes an effect only if the condition is `false`.
83208427
*

packages/effect/src/internal/core.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3054,6 +3054,68 @@ export const mapInputContext = dual<
30543054
f: (context: Context.Context<R2>) => Context.Context<R>
30553055
) => contextWithEffect((context: Context.Context<R2>) => provideContext(self, f(context))))
30563056

3057+
// -----------------------------------------------------------------------------
3058+
// Filtering
3059+
// -----------------------------------------------------------------------------
3060+
3061+
/** @internal */
3062+
export const filterEffectOrElse: {
3063+
<A, E2, R2, A2, E3, R3>(
3064+
options: {
3065+
readonly predicate: (a: NoInfer<A>) => Effect.Effect<boolean, E2, R2>
3066+
readonly orElse: (a: NoInfer<A>) => Effect.Effect<A2, E3, R3>
3067+
}
3068+
): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A | A2, E | E2 | E3, R | R2 | R3>
3069+
<A, E, R, E2, R2, A2, E3, R3>(
3070+
self: Effect.Effect<A, E, R>,
3071+
options: {
3072+
readonly predicate: (a: A) => Effect.Effect<boolean, E2, R2>
3073+
readonly orElse: (a: A) => Effect.Effect<A2, E3, R3>
3074+
}
3075+
): Effect.Effect<A | A2, E | E2 | E3, R | R2 | R3>
3076+
} = dual(2, <A, E, R, E2, R2, A2, E3, R3>(
3077+
self: Effect.Effect<A, E, R>,
3078+
options: {
3079+
readonly predicate: (a: A) => Effect.Effect<boolean, E2, R2>
3080+
readonly orElse: (a: A) => Effect.Effect<A2, E3, R3>
3081+
}
3082+
): Effect.Effect<A | A2, E | E2 | E3, R | R2 | R3> =>
3083+
flatMap(
3084+
self,
3085+
(a) =>
3086+
flatMap(
3087+
options.predicate(a),
3088+
(pass): Effect.Effect<A | A2, E3, R3> => pass ? succeed(a) : options.orElse(a)
3089+
)
3090+
))
3091+
3092+
/** @internal */
3093+
export const filterEffectOrFail: {
3094+
<A, E2, R2, E3>(
3095+
options: {
3096+
readonly predicate: (a: NoInfer<A>) => Effect.Effect<boolean, E2, R2>
3097+
readonly orFailWith: (a: NoInfer<A>) => E3
3098+
}
3099+
): <E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2 | E3, R | R2>
3100+
<A, E, R, E2, R2, E3>(
3101+
self: Effect.Effect<A, E, R>,
3102+
options: {
3103+
readonly predicate: (a: A) => Effect.Effect<boolean, E2, R2>
3104+
readonly orFailWith: (a: A) => E3
3105+
}
3106+
): Effect.Effect<A, E | E2 | E3, R | R2>
3107+
} = dual(2, <A, E, R, E2, R2, E3>(
3108+
self: Effect.Effect<A, E, R>,
3109+
options: {
3110+
readonly predicate: (a: A) => Effect.Effect<boolean, E2, R2>
3111+
readonly orFailWith: (a: A) => E3
3112+
}
3113+
): Effect.Effect<A, E | E2 | E3, R | R2> =>
3114+
filterEffectOrElse(self, {
3115+
predicate: options.predicate,
3116+
orElse: (a) => fail(options.orFailWith(a))
3117+
}))
3118+
30573119
// -----------------------------------------------------------------------------
30583120
// Tracing
30593121
// -----------------------------------------------------------------------------

packages/effect/test/Effect/filtering.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ describe("Effect", () => {
189189
assertRight(goodCase, 0)
190190
assertLeft(badCase, Either.left("predicate failed, got 1!"))
191191
}))
192+
192193
it.effect("filterOrFail - without orFailWith", () =>
193194
Effect.gen(function*() {
194195
const goodCase = yield* pipe(
@@ -207,4 +208,31 @@ describe("Effect", () => {
207208
deepStrictEqual(goodCaseDataFirst, 0)
208209
deepStrictEqual(badCase, new Cause.NoSuchElementException())
209210
}))
211+
212+
describe("filterEffectOrElse", () => {
213+
it.effect("executes fallback", () =>
214+
Effect.gen(function*() {
215+
const result = yield* Effect.succeed(1).pipe(
216+
Effect.filterEffectOrElse({
217+
predicate: (n) => Effect.succeed(n === 0),
218+
orElse: () => Effect.succeed(0)
219+
})
220+
)
221+
assert.strictEqual(result, 0)
222+
}))
223+
})
224+
225+
describe("filterEffectOrFails", () => {
226+
it.effect("executes orFailWith", () =>
227+
Effect.gen(function*() {
228+
const result = yield* Effect.succeed(1).pipe(
229+
Effect.filterEffectOrElse({
230+
predicate: (n) => Effect.succeed(n === 0),
231+
orElse: () => Effect.fail("boom")
232+
}),
233+
Effect.flip
234+
)
235+
assert.strictEqual(result, "boom")
236+
}))
237+
})
210238
})

0 commit comments

Comments
 (0)