Skip to content

Commit

Permalink
feat: add onlyEffect option to Effect.tap (#3292)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Arnaldi <michael.arnaldi@effectful.co>
  • Loading branch information
2 people authored and tim-smart committed Jul 30, 2024
1 parent 2d09078 commit 4bce5a0
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/pink-ghosts-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

Add onlyEffect option to Effect.tap
12 changes: 12 additions & 0 deletions packages/effect/dtslint/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@ Effect.succeed("a" as const).pipe(Effect.filterOrElse(
// $ExpectType Effect<"a", never, never>
Effect.succeed("a" as const).pipe(Effect.tap(tacitString))

// $ExpectType Effect<"a", never, never>
Effect.succeed("a" as const).pipe(Effect.tap(tacitString, { onlyEffect: true }))

// @ts-expect-error
Effect.succeed("a" as const).pipe(Effect.tap(tacitStringError, { onlyEffect: true }))

// $ExpectType Effect<"a", never, never>
Effect.succeed("a" as const).pipe(Effect.tap(tacitString("a"), { onlyEffect: true }))

// @ts-expect-error
Effect.succeed("a" as const).pipe(Effect.tap("a", { onlyEffect: true }))

// $ExpectType Effect<never, "a", never>
Effect.fail("a" as const).pipe(Effect.tapError(tacitString))

Expand Down
22 changes: 22 additions & 0 deletions packages/effect/src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4029,25 +4029,47 @@ export const tap: {
) => [X] extends [Effect<infer _A1, infer E1, infer R1>] ? Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect<A, E | Cause.UnknownException, R>
: Effect<A, E, R>
<A, X, E1, R1>(
f: (a: NoInfer<A>) => Effect<X, E1, R1>,
options: { onlyEffect: true }
): <E, R>(
self: Effect<A, E, R>
) => Effect<A, E | E1, R | R1>
<X>(
f: NotFunction<X>
): <A, E, R>(
self: Effect<A, E, R>
) => [X] extends [Effect<infer _A1, infer E1, infer R1>] ? Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect<A, E | Cause.UnknownException, R>
: Effect<A, E, R>
<X, E1, R1>(
f: Effect<X, E1, R1>,
options: { onlyEffect: true }
): <A, E, R>(
self: Effect<A, E, R>
) => Effect<A, E | E1, R | R1>
<A, E, R, X>(
self: Effect<A, E, R>,
f: (a: NoInfer<A>) => X
): [X] extends [Effect<infer _A1, infer E1, infer R1>] ? Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect<A, E | Cause.UnknownException, R>
: Effect<A, E, R>
<A, E, R, X, E1, R1>(
self: Effect<A, E, R>,
f: (a: NoInfer<A>) => Effect<X, E1, R1>,
options: { onlyEffect: true }
): Effect<A, E | E1, R | R1>
<A, E, R, X>(
self: Effect<A, E, R>,
f: NotFunction<X>
): [X] extends [Effect<infer _A1, infer E1, infer R1>] ? Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect<A, E | Cause.UnknownException, R>
: Effect<A, E, R>
<A, E, R, X, E1, R1>(
self: Effect<A, E, R>,
f: Effect<X, E1, R1>,
options: { onlyEffect: true }
): Effect<A, E | E1, R | R1>
} = core.tap

/**
Expand Down
49 changes: 37 additions & 12 deletions packages/effect/src/internal/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1250,13 +1250,25 @@ export const tap = dual<
) => [X] extends [Effect.Effect<infer _A1, infer E1, infer R1>] ? Effect.Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect.Effect<A, E | Cause.UnknownException, R>
: Effect.Effect<A, E, R>
<A, X, E1, R1>(
f: (a: NoInfer<A>) => Effect.Effect<X, E1, R1>,
options: { onlyEffect: true }
): <E, R>(
self: Effect.Effect<A, E, R>
) => Effect.Effect<A, E | E1, R | R1>
<X>(
f: NotFunction<X>
): <A, E, R>(
self: Effect.Effect<A, E, R>
) => [X] extends [Effect.Effect<infer _A1, infer E1, infer R1>] ? Effect.Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect.Effect<A, E | Cause.UnknownException, R>
: Effect.Effect<A, E, R>
<X, E1, R1>(
f: Effect.Effect<X, E1, R1>,
options: { onlyEffect: true }
): <A, E, R>(
self: Effect.Effect<A, E, R>
) => Effect.Effect<A, E | E1, R | R1>
},
{
<A, E, R, X>(
Expand All @@ -1265,25 +1277,38 @@ export const tap = dual<
): [X] extends [Effect.Effect<infer _A1, infer E1, infer R1>] ? Effect.Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect.Effect<A, E | Cause.UnknownException, R>
: Effect.Effect<A, E, R>
<A, E, R, X, E1, R1>(
self: Effect.Effect<A, E, R>,
f: (a: NoInfer<A>) => Effect.Effect<X, E1, R1>,
options: { onlyEffect: true }
): Effect.Effect<A, E | E1, R | R1>
<A, E, R, X>(
self: Effect.Effect<A, E, R>,
f: NotFunction<X>
): [X] extends [Effect.Effect<infer _A1, infer E1, infer R1>] ? Effect.Effect<A, E | E1, R | R1>
: [X] extends [PromiseLike<infer _A1>] ? Effect.Effect<A, E | Cause.UnknownException, R>
: Effect.Effect<A, E, R>
<A, E, R, X, E1, R1>(
self: Effect.Effect<A, E, R>,
f: Effect.Effect<X, E1, R1>,
options: { onlyEffect: true }
): Effect.Effect<A, E | E1, R | R1>
}
>(2, (self, f) =>
flatMap(self, (a) => {
const b = typeof f === "function" ? (f as any)(a) : f
if (isEffect(b)) {
return as(b, a)
} else if (isPromiseLike(b)) {
return async<any, Cause.UnknownException>((resume) => {
b.then((_) => resume(succeed(a)), (e) => resume(fail(new UnknownException(e))))
})
}
return succeed(a)
}))
>(
(args) => args.length === 3 || args.length === 2 && !(isObject(args[1]) && "onlyEffect" in args[1]),
<A, E, R, X>(self: Effect.Effect<A, E, R>, f: X) =>
flatMap(self, (a) => {
const b = typeof f === "function" ? (f as any)(a) : f
if (isEffect(b)) {
return as(b, a)
} else if (isPromiseLike(b)) {
return async<any, Cause.UnknownException>((resume) => {
b.then((_) => resume(succeed(a)), (e) => resume(fail(new UnknownException(e))))
})
}
return succeed(a)
})
)

/* @internal */
export const transplant = <A, E, R>(
Expand Down
44 changes: 25 additions & 19 deletions packages/effect/test/Effect/sequencing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,33 @@ describe("Effect", () => {
assert.strictEqual(yield* $(a9), "ok")
}))
it.effect("tap", () =>
Effect.gen(function*($) {
Effect.gen(function*() {
const a0 = Effect.tap(Effect.succeed(0), Effect.succeed(1))
const a1 = Effect.succeed(0).pipe(Effect.tap(Effect.succeed(1)))
const a2 = Effect.tap(Effect.succeed(0), (n) => Effect.succeed(n + 1))
const a3 = Effect.succeed(0).pipe(Effect.tap((n) => Effect.succeed(n + 1)))
const a4 = Effect.succeed(0).pipe(Effect.tap("ok"))
const a5 = Effect.succeed(0).pipe(Effect.tap(() => "ok"))
const a6 = Effect.tap(Effect.succeed(0), () => "ok")
const a7 = Effect.tap(Effect.succeed(0), "ok")
const a8 = Effect.tap(Effect.succeed(0), () => Promise.resolve("ok"))
const a9 = Effect.tap(Effect.succeed(0), Promise.resolve("ok"))
assert.strictEqual(yield* $(a0), 0)
assert.strictEqual(yield* $(a1), 0)
assert.strictEqual(yield* $(a2), 0)
assert.strictEqual(yield* $(a3), 0)
assert.strictEqual(yield* $(a4), 0)
assert.strictEqual(yield* $(a5), 0)
assert.strictEqual(yield* $(a6), 0)
assert.strictEqual(yield* $(a7), 0)
assert.strictEqual(yield* $(a8), 0)
assert.strictEqual(yield* $(a9), 0)
const a2 = Effect.succeed(0).pipe(Effect.tap(Effect.succeed(1), { onlyEffect: true }))
const a3 = Effect.tap(Effect.succeed(0), (n) => Effect.succeed(n + 1))
const a4 = Effect.tap(Effect.succeed(0), (n) => Effect.succeed(n + 1), { onlyEffect: true })
const a5 = Effect.succeed(0).pipe(Effect.tap((n) => Effect.succeed(n + 1)))
const a6 = Effect.succeed(0).pipe(Effect.tap((n) => Effect.succeed(n + 1), { onlyEffect: true }))
const a7 = Effect.succeed(0).pipe(Effect.tap("ok"))
const a8 = Effect.succeed(0).pipe(Effect.tap(() => "ok"))
const a9 = Effect.tap(Effect.succeed(0), () => "ok")
const a10 = Effect.tap(Effect.succeed(0), "ok")
const a11 = Effect.tap(Effect.succeed(0), () => Promise.resolve("ok"))
const a12 = Effect.tap(Effect.succeed(0), Promise.resolve("ok"))
assert.strictEqual(yield* a0, 0)
assert.strictEqual(yield* a1, 0)
assert.strictEqual(yield* a2, 0)
assert.strictEqual(yield* a3, 0)
assert.strictEqual(yield* a4, 0)
assert.strictEqual(yield* a5, 0)
assert.strictEqual(yield* a6, 0)
assert.strictEqual(yield* a7, 0)
assert.strictEqual(yield* a8, 0)
assert.strictEqual(yield* a9, 0)
assert.strictEqual(yield* a10, 0)
assert.strictEqual(yield* a11, 0)
assert.strictEqual(yield* a12, 0)
}))
it.effect("flattens nested effects", () =>
Effect.gen(function*($) {
Expand Down

0 comments on commit 4bce5a0

Please sign in to comment.