Skip to content

Commit 35db9ce

Browse files
LaureRCtim-smart
authored andcommitted
Add Effect.transposeMapOption (#4597)
1 parent 1f47e4e commit 35db9ce

File tree

4 files changed

+143
-0
lines changed

4 files changed

+143
-0
lines changed

.changeset/small-ties-love.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": minor
3+
---
4+
5+
Add Effect.transposeMapOption

packages/effect/dtslint/Effect.tst.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,4 +1392,37 @@ describe("Effect", () => {
13921392
Effect.Effect<Option.Option<string>, "err-1", "dep-1">
13931393
>()
13941394
})
1395+
1396+
it("transposeMapOption", () => {
1397+
expect(Effect.transposeMapOption(Option.none(), (value) => {
1398+
expect(value).type.toBe<never>()
1399+
return string
1400+
})).type.toBe<
1401+
Effect.Effect<Option.Option<string>, "err-1", "dep-1">
1402+
>()
1403+
expect(pipe(
1404+
Option.none(),
1405+
Effect.transposeMapOption((value) => {
1406+
expect(value).type.toBe<never>()
1407+
return string
1408+
})
1409+
)).type.toBe<
1410+
Effect.Effect<Option.Option<string>, "err-1", "dep-1">
1411+
>()
1412+
expect(Effect.transposeMapOption(Option.some(42), (value) => {
1413+
expect(value).type.toBe<number>()
1414+
return string
1415+
})).type.toBe<
1416+
Effect.Effect<Option.Option<string>, "err-1", "dep-1">
1417+
>()
1418+
expect(pipe(
1419+
Option.some(42),
1420+
Effect.transposeMapOption((value) => {
1421+
expect(value).type.toBe<number>()
1422+
return string
1423+
})
1424+
)).type.toBe<
1425+
Effect.Effect<Option.Option<string>, "err-1", "dep-1">
1426+
>()
1427+
})
13951428
})

packages/effect/src/Effect.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13263,6 +13263,50 @@ export const transposeOption = <A = never, E = never, R = never>(
1326313263
return option_.isNone(self) ? succeedNone : map(self.value, option_.some)
1326413264
}
1326513265

13266+
/**
13267+
* Applies an `Effect` on an `Option` and transposes the result.
13268+
*
13269+
* **Details**
13270+
*
13271+
* If the `Option` is `None`, the resulting `Effect` will immediately succeed with a `None` value.
13272+
* If the `Option` is `Some`, the effectful operation will be executed on the inner value, and its result wrapped in a `Some`.
13273+
*
13274+
* @example
13275+
* ```ts
13276+
* import { Effect, Option, pipe } from "effect"
13277+
*
13278+
* // ┌─── Effect<Option<number>, never, never>>
13279+
* // ▼
13280+
* const noneResult = pipe(
13281+
* Option.none(),
13282+
* Effect.transposeMapOption(() => Effect.succeed(42)) // will not be executed
13283+
* )
13284+
* console.log(Effect.runSync(noneResult))
13285+
* // Output: { _id: 'Option', _tag: 'None' }
13286+
*
13287+
* // ┌─── Effect<Option<number>, never, never>>
13288+
* // ▼
13289+
* const someSuccessResult = pipe(
13290+
* Option.some(42),
13291+
* Effect.transposeMapOption((value) => Effect.succeed(value * 2))
13292+
* )
13293+
* console.log(Effect.runSync(someSuccessResult))
13294+
* // Output: { _id: 'Option', _tag: 'Some', value: 84 }
13295+
* ```
13296+
*
13297+
* @since 3.14.0
13298+
* @category Optional Wrapping & Unwrapping
13299+
*/
13300+
export const transposeMapOption = dual<
13301+
<A, B, E = never, R = never>(
13302+
f: (self: A) => Effect<B, E, R>
13303+
) => (self: Option.Option<A>) => Effect<Option.Option<B>, E, R>,
13304+
<A, B, E = never, R = never>(
13305+
self: Option.Option<A>,
13306+
f: (self: A) => Effect<B, E, R>
13307+
) => Effect<Option.Option<B>, E, R>
13308+
>(2, (self, f) => option_.isNone(self) ? succeedNone : map(f(self.value), option_.some))
13309+
1326613310
/**
1326713311
* @since 2.0.0
1326813312
* @category Models

packages/effect/test/Effect/optional-wrapping-unwrapping.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { assert, describe, it } from "@effect/vitest"
22
import * as Effect from "effect/Effect"
33
import * as Either from "effect/Either"
4+
import { pipe } from "effect/Function"
45
import * as Option from "effect/Option"
56

67
describe("Effect", () => {
@@ -17,6 +18,66 @@ describe("Effect", () => {
1718
assert.deepStrictEqual(result, Option.some(42))
1819
}))
1920
})
21+
describe("transposeMapOption", () => {
22+
describe("None", () => {
23+
it.effect("Success", () =>
24+
Effect.gen(function*() {
25+
const resultDataFirst = yield* Effect.transposeMapOption(Option.none(), () => Effect.succeed(42))
26+
assert.ok(Option.isNone(resultDataFirst))
27+
28+
const resultDataLast = yield* pipe(
29+
Option.none(),
30+
Effect.transposeMapOption(() => Effect.succeed(42))
31+
)
32+
assert.ok(Option.isNone(resultDataLast))
33+
}))
34+
it.effect("Failure", () =>
35+
Effect.gen(function*() {
36+
const resultDataFirst = yield* Effect.transposeMapOption(Option.none(), () => Effect.fail("Error"))
37+
assert.ok(Option.isNone(resultDataFirst))
38+
39+
const resultDataLast = yield* pipe(
40+
Option.none(),
41+
Effect.transposeMapOption(() => Effect.fail("Error"))
42+
)
43+
assert.ok(Option.isNone(resultDataLast))
44+
}))
45+
})
46+
47+
describe("Some", () => {
48+
describe("None", () => {
49+
it.effect("Success", () =>
50+
Effect.gen(function*() {
51+
const resultDataFirst = yield* Effect.transposeMapOption(Option.some(42), (value) =>
52+
Effect.succeed(value * 2))
53+
assert.deepStrictEqual(resultDataFirst, Option.some(84))
54+
55+
const resultDataLast = yield* pipe(
56+
Option.some(42),
57+
Effect.transposeMapOption((value) =>
58+
Effect.succeed(value * 2)
59+
)
60+
)
61+
assert.deepStrictEqual(resultDataLast, Option.some(84))
62+
}))
63+
it.effect("Failure", () =>
64+
Effect.gen(function*() {
65+
const resultDataFirst = yield* pipe(
66+
Effect.transposeMapOption(Option.some(42), () => Effect.fail("error")),
67+
Effect.flip
68+
)
69+
assert.equal(resultDataFirst, "error")
70+
71+
const resultDataLast = yield* pipe(
72+
Option.some(42),
73+
Effect.transposeMapOption(() => Effect.fail("error")),
74+
Effect.flip
75+
)
76+
assert.equal(resultDataLast, "error")
77+
}))
78+
})
79+
})
80+
})
2081
})
2182

2283
describe("Either", () => {

0 commit comments

Comments
 (0)