diff --git a/.changeset/pink-ghosts-suffer.md b/.changeset/pink-ghosts-suffer.md new file mode 100644 index 0000000000..f2eff72d0c --- /dev/null +++ b/.changeset/pink-ghosts-suffer.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Add onlyEffect option to Effect.tap diff --git a/packages/effect/dtslint/Effect.ts b/packages/effect/dtslint/Effect.ts index 813fa1f7a4..2ca31e3124 100644 --- a/packages/effect/dtslint/Effect.ts +++ b/packages/effect/dtslint/Effect.ts @@ -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 Effect.fail("a" as const).pipe(Effect.tapError(tacitString)) diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index 276daeda32..d37b318362 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -4029,6 +4029,12 @@ export const tap: { ) => [X] extends [Effect] ? Effect : [X] extends [PromiseLike] ? Effect : Effect + ( + f: (a: NoInfer) => Effect, + options: { onlyEffect: true } + ): ( + self: Effect + ) => Effect ( f: NotFunction ): ( @@ -4036,18 +4042,34 @@ export const tap: { ) => [X] extends [Effect] ? Effect : [X] extends [PromiseLike] ? Effect : Effect + ( + f: Effect, + options: { onlyEffect: true } + ): ( + self: Effect + ) => Effect ( self: Effect, f: (a: NoInfer) => X ): [X] extends [Effect] ? Effect : [X] extends [PromiseLike] ? Effect : Effect + ( + self: Effect, + f: (a: NoInfer) => Effect, + options: { onlyEffect: true } + ): Effect ( self: Effect, f: NotFunction ): [X] extends [Effect] ? Effect : [X] extends [PromiseLike] ? Effect : Effect + ( + self: Effect, + f: Effect, + options: { onlyEffect: true } + ): Effect } = core.tap /** diff --git a/packages/effect/src/internal/core.ts b/packages/effect/src/internal/core.ts index d0ae961aca..9b2815eee8 100644 --- a/packages/effect/src/internal/core.ts +++ b/packages/effect/src/internal/core.ts @@ -1250,6 +1250,12 @@ export const tap = dual< ) => [X] extends [Effect.Effect] ? Effect.Effect : [X] extends [PromiseLike] ? Effect.Effect : Effect.Effect + ( + f: (a: NoInfer) => Effect.Effect, + options: { onlyEffect: true } + ): ( + self: Effect.Effect + ) => Effect.Effect ( f: NotFunction ): ( @@ -1257,6 +1263,12 @@ export const tap = dual< ) => [X] extends [Effect.Effect] ? Effect.Effect : [X] extends [PromiseLike] ? Effect.Effect : Effect.Effect + ( + f: Effect.Effect, + options: { onlyEffect: true } + ): ( + self: Effect.Effect + ) => Effect.Effect }, { ( @@ -1265,25 +1277,38 @@ export const tap = dual< ): [X] extends [Effect.Effect] ? Effect.Effect : [X] extends [PromiseLike] ? Effect.Effect : Effect.Effect + ( + self: Effect.Effect, + f: (a: NoInfer) => Effect.Effect, + options: { onlyEffect: true } + ): Effect.Effect ( self: Effect.Effect, f: NotFunction ): [X] extends [Effect.Effect] ? Effect.Effect : [X] extends [PromiseLike] ? Effect.Effect : Effect.Effect + ( + self: Effect.Effect, + f: Effect.Effect, + options: { onlyEffect: true } + ): Effect.Effect } ->(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((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]), + (self: Effect.Effect, 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((resume) => { + b.then((_) => resume(succeed(a)), (e) => resume(fail(new UnknownException(e)))) + }) + } + return succeed(a) + }) +) /* @internal */ export const transplant = ( diff --git a/packages/effect/test/Effect/sequencing.test.ts b/packages/effect/test/Effect/sequencing.test.ts index 8207e5aa2b..1aeb59a094 100644 --- a/packages/effect/test/Effect/sequencing.test.ts +++ b/packages/effect/test/Effect/sequencing.test.ts @@ -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*($) {