Skip to content

Commit

Permalink
Add Effect.transposeOption, closes #3142 (#4284)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored and effect-bot committed Jan 23, 2025
1 parent 7e4b8d1 commit 2cb96fe
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 2 deletions.
31 changes: 31 additions & 0 deletions .changeset/cyan-radios-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
"effect": minor
---

Add `Effect.transposeOption`, closes #3142.

Converts an `Option` of an `Effect` into an `Effect` of an `Option`.

**Details**

This function transforms an `Option<Effect<A, E, R>>` into an
`Effect<Option<A>, E, R>`. If the `Option` is `None`, the resulting `Effect`
will immediately succeed with a `None` value. If the `Option` is `Some`, the
inner `Effect` will be executed, and its result wrapped in a `Some`.

**Example**

```ts
import { Effect, Option } from "effect"

// ┌─── Option<Effect<number, never, never>>
//
const maybe = Option.some(Effect.succeed(42))

// ┌─── Effect<Option<number>, never, never>
//
const result = Effect.transposeOption(maybe)

console.log(Effect.runSync(result))
// Output: { _id: 'Option', _tag: 'Some', value: 42 }
```
10 changes: 10 additions & 0 deletions packages/effect/dtslint/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1350,3 +1350,13 @@ hole<
}>
>
>()

// -------------------------------------------------------------------------------------
// transposeOption
// -------------------------------------------------------------------------------------

// $ExpectType Effect<Option<never>, never, never>
Effect.transposeOption(Option.none())

// $ExpectType Effect<Option<string>, "err-1", "dep-1">
Effect.transposeOption(Option.some(string))
40 changes: 38 additions & 2 deletions packages/effect/src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import * as defaultServices from "./internal/defaultServices.js"
import * as circular from "./internal/effect/circular.js"
import * as fiberRuntime from "./internal/fiberRuntime.js"
import * as layer from "./internal/layer.js"
import * as option_ from "./internal/option.js"
import * as query from "./internal/query.js"
import * as runtime_ from "./internal/runtime.js"
import * as schedule_ from "./internal/schedule.js"
Expand Down Expand Up @@ -12811,7 +12812,7 @@ export const withParentSpan: {
* ```
*
* @since 2.0.0
* @category Optional Wrapping
* @category Optional Wrapping & Unwrapping
*/
export const fromNullable: <A>(value: A) => Effect<NonNullable<A>, Cause.NoSuchElementException> = effect.fromNullable

Expand Down Expand Up @@ -12866,12 +12867,47 @@ export const fromNullable: <A>(value: A) => Effect<NonNullable<A>, Cause.NoSuchE
* ```
*
* @since 2.0.0
* @category Optional Wrapping
* @category Optional Wrapping & Unwrapping
*/
export const optionFromOptional: <A, E, R>(
self: Effect<A, E, R>
) => Effect<Option.Option<A>, Exclude<E, Cause.NoSuchElementException>, R> = effect.optionFromOptional

/**
* Converts an `Option` of an `Effect` into an `Effect` of an `Option`.
*
* **Details**
*
* This function transforms an `Option<Effect<A, E, R>>` into an
* `Effect<Option<A>, E, R>`. If the `Option` is `None`, the resulting `Effect`
* will immediately succeed with a `None` value. If the `Option` is `Some`, the
* inner `Effect` will be executed, and its result wrapped in a `Some`.
*
* @example
* ```ts
* import { Effect, Option } from "effect"
*
* // ┌─── Option<Effect<number, never, never>>
* // ▼
* const maybe = Option.some(Effect.succeed(42))
*
* // ┌─── Effect<Option<number>, never, never>
* // ▼
* const result = Effect.transposeOption(maybe)
*
* console.log(Effect.runSync(result))
* // Output: { _id: 'Option', _tag: 'Some', value: 42 }
* ```
*
* @since 3.13.0
* @category Optional Wrapping & Unwrapping
*/
export const transposeOption = <A = never, E = never, R = never>(
self: Option.Option<Effect<A, E, R>>
): Effect<Option.Option<A>, E, R> => {
return option_.isNone(self) ? succeedNone : map(self.value, option_.some)
}

/**
* @since 2.0.0
* @category Models
Expand Down
20 changes: 20 additions & 0 deletions packages/effect/test/Effect/optional-wrapping-unwrapping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as Effect from "effect/Effect"
import * as Option from "effect/Option"
import * as it from "effect/test/utils/extend"
import { assert, describe } from "vitest"

describe("Effect", () => {
describe("transposeOption", () => {
it.effect("None", () =>
Effect.gen(function*() {
const result = yield* Effect.transposeOption(Option.none())
assert.ok(Option.isNone(result))
}))

it.effect("Some", () =>
Effect.gen(function*() {
const result = yield* Effect.transposeOption(Option.some(Effect.succeed(42)))
assert.deepStrictEqual(result, Option.some(42))
}))
})
})

0 comments on commit 2cb96fe

Please sign in to comment.