Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions content/blog/0003-release-2.3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,10 @@ class ApiLimiter extends Context.Tag("@services/ApiLimiter")<
)
}

const program = Effect.gen(function*($) {
const rateLimit = yield* $(ApiLimiter)
const program = Effect.gen(function* () {
const rateLimit = yield* ApiLimiter
for (let n = 0; n < 100; n++) {
yield* $(rateLimit(Effect.log("Calling RateLimited Effect")))
yield* rateLimit(Effect.log("Calling RateLimited Effect"))
}
})

Expand Down
4 changes: 2 additions & 2 deletions content/blog/0010-schema-release-0.64.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -635,8 +635,8 @@ class Messages extends Context.Tag("Messages")<

const Name = S.NonEmpty.pipe(
S.message(() =>
Effect.gen(function* (_) {
const service = yield* _(Effect.serviceOption(Messages))
Effect.gen(function* () {
const service = yield* Effect.serviceOption(Messages)
return Option.match(service, {
onNone: () => "Invalid string",
onSome: (messages) => messages.NonEmpty
Expand Down
94 changes: 23 additions & 71 deletions content/docs/400-guides/100-essentials/500-using-generators.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Using Generators in Effect
excerpt: Explore the syntax of using generators in Effect to write effectful code. Learn about the `Effect.gen` function and its `_` helper, which facilitates yielding effects. Compare `Effect.gen` with `async`/`await` for writing asynchronous code. Understand how generators enhance control flow, handle errors, and utilize short-circuiting in effectful programs. Discover the flexibility of using the `_` helper as a pipe function and passing references to `this` in generator functions.
excerpt: Explore the syntax of using generators in Effect to write effectful code. Learn about the `Effect.gen` function. Compare `Effect.gen` with `async`/`await` for writing asynchronous code. Understand how generators enhance control flow, handle errors, and utilize short-circuiting in effectful programs. Discover passing references to `this` in generator functions.
bottomNavigation: pagination
---

Expand Down Expand Up @@ -32,10 +32,10 @@ const task1 = Effect.promise(() => Promise.resolve(10))

const task2 = Effect.promise(() => Promise.resolve(2))

export const program = Effect.gen(function* (_) {
const a = yield* _(task1)
const b = yield* _(task2)
const n1 = yield* _(divide(a, b))
export const program = Effect.gen(function* () {
const a = yield* task1
const b = yield* task2
const n1 = yield* divide(a, b)
const n2 = increment(n1)
return `Result is: ${n2}`
})
Expand Down Expand Up @@ -65,25 +65,17 @@ const task1 = Effect.promise(() => Promise.resolve(10))

const task2 = Effect.promise(() => Promise.resolve(2))
// ---cut---
export const program = Effect.gen(function* (_) {
const a = yield* _(task1)
const b = yield* _(task2)
const n1 = yield* _(divide(a, b))
export const program = Effect.gen(function* () {
const a = yield* task1
const b = yield* task2
const n1 = yield* divide(a, b)
const n2 = increment(n1)
return `Result is: ${n2}`
})
```

...TypeScript can accurately infer the types associated with that effect. This ensures that your code is type-safe and helps prevent potential errors.

Additionally, the `_` function acts as an adapter between Effect and the JavaScript world, particularly the [iterable protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). This adapter allows you to seamlessly leverage the [`yield*`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*) keyword from JavaScript's generator syntax within Effect's generator functions.

<Info>
The `_` symbol is just a convention for the argument name and is not a
special symbol in Effect. You are free to use any name you prefer (e.g.,
`$`, etc...). The current convention is to use `_` as the argument name.
</Info>

## Comparing Effect.gen with async/await

If you are familiar with `async`/`await`, you may notice that the flow of writing code is similar.
Expand All @@ -107,10 +99,10 @@ const task1 = Effect.promise(() => Promise.resolve(10))

const task2 = Effect.promise(() => Promise.resolve(2))

export const program = Effect.gen(function* (_) {
const a = yield* _(task1)
const b = yield* _(task2)
const n1 = yield* _(divide(a, b))
export const program = Effect.gen(function* () {
const a = yield* task1
const b = yield* task2
const n1 = yield* divide(a, b)
const n2 = increment(n1)
return `Result is: ${n2}`
})
Expand Down Expand Up @@ -163,15 +155,15 @@ const divide = (a: number, b: number): Effect.Effect<number, Error> =>
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)

const program = Effect.gen(function* (_) {
const program = Effect.gen(function* () {
let i = 1

while (true) {
if (i === 10) {
break
} else {
if (i % 2 === 0) {
console.log(yield* _(divide(12, i)))
console.log(yield* divide(12, i))
}
i++
continue
Expand All @@ -196,10 +188,10 @@ Within the realm of the `Effect.gen` API, you have the capability to introduce e
```ts twoslash
import { Effect } from "effect"

const program = Effect.gen(function* (_) {
const program = Effect.gen(function* () {
console.log("Task1...")
console.log("Task2...")
yield* _(Effect.fail("Something went wrong!"))
yield* Effect.fail("Something went wrong!")
})

Effect.runPromiseExit(program).then(console.log)
Expand Down Expand Up @@ -229,10 +221,10 @@ In simpler terms, the short-circuiting behavior ensures that if something goes w
```ts twoslash
import { Effect } from "effect"

const program = Effect.gen(function* (_) {
const program = Effect.gen(function* () {
console.log("Task1...")
console.log("Task2...")
yield* _(Effect.fail("Something went wrong!"))
yield* Effect.fail("Something went wrong!")
console.log("This won't be executed")
})

Expand All @@ -255,44 +247,6 @@ Task2...
section.
</Info>

## Using the helper as a pipe

The `_` helper can also be used as a `pipe` function (see [Building Pipelines](./pipeline) for more information), allowing you to mix and match different styles of writing code within `Effect.gen` if needed.

In the following example, the `Random.next()` effect is piped into the `Effect.map` function:

```ts twoslash {5-6}
import { Effect, Random } from "effect"

const program = Effect.gen(function* (_) {
const n = yield* _(
Random.next,
Effect.map((n) => n * 2)
)
if (n > 0.5) {
return "yay!"
} else {
return yield* _(Effect.fail("oh no!"))
}
})
```

This approach is useful to avoid excessive notation by using both the `_` helper and the `pipe` function. Instead, you can directly pass the `Random.next()` effect to `Effect.map` within the `_` helper, eliminating the need for an additional `pipe` function:

```ts twoslash
import { Effect, Random, pipe } from "effect"
// ---cut---
const program = Effect.gen(function* (_) {
const n = yield* _(
pipe(
Random.next,
Effect.map((n) => n * 2)
)
)
// ...
})
```

## Passing this

In some cases, you might need to pass a reference to the current object (`this`) into the body of your generator function. You can achieve this by utilizing an overload that accepts the reference as the first argument:
Expand All @@ -302,14 +256,12 @@ import { Effect } from "effect"

class MyService {
readonly local = 1
compute() {
return Effect.gen(this, function* (_) {
return yield* _(Effect.succeed(this.local + 1))
})
}
compute = Effect.gen(this, function* () {
return yield* Effect.succeed(this.local + 1)
})
}

console.log(Effect.runSync(new MyService().compute())) // Output: 2
console.log(Effect.runSync(new MyService().compute)) // Output: 2
```

In this example, we have a `MyService` class with a property called `local`. By passing `this` as the first argument to `Effect.gen`, we make the `local` property available within the generator.
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,15 @@ import { Effect, Either, Console } from "effect"

const simulatedTask = Effect.fail("Oh uh!").pipe(Effect.as(2))

const program = Effect.gen(function* (_) {
const failureOrSuccess = yield* _(Effect.either(simulatedTask))
const program = Effect.gen(function* () {
const failureOrSuccess = yield* Effect.either(simulatedTask)
if (Either.isLeft(failureOrSuccess)) {
const error = failureOrSuccess.left
yield* _(Console.log(`failure: ${error}`))
yield* Console.log(`failure: ${error}`)
return 0
} else {
const value = failureOrSuccess.right
yield* _(Console.log(`success: ${value}`))
yield* Console.log(`success: ${value}`)
return value
}
})
Expand Down Expand Up @@ -227,9 +227,9 @@ import { Effect, Console } from "effect"

const simulatedTask = Effect.fail("Oh uh!").pipe(Effect.as(2))

const program = Effect.gen(function* (_) {
const cause = yield* _(Effect.cause(simulatedTask))
yield* _(Console.log(cause))
const program = Effect.gen(function* () {
const cause = yield* Effect.cause(simulatedTask)
yield* Console.log(cause)
})
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { Effect, Data } from "effect"

class MyError extends Data.Error<{ message: string }> {}

export const program = Effect.gen(function* (_) {
yield* _(new MyError({ message: "Oh no!" })) // same as yield* _(Effect.fail(new MyError({ message: "Oh no!" })))
export const program = Effect.gen(function* () {
yield* new MyError({ message: "Oh no!" }) // same as yield* Effect.fail(new MyError({ message: "Oh no!" })
})

Effect.runPromiseExit(program).then(console.log)
Expand Down Expand Up @@ -47,13 +47,13 @@ class BarError extends Data.TaggedError("Bar")<{
randomNumber: number
}> {}

export const program = Effect.gen(function* (_) {
const n = yield* _(Random.next)
export const program = Effect.gen(function* () {
const n = yield* Random.next
return n > 0.5
? "yay!"
: n < 0.2
? yield* _(new FooError({ message: "Oh no!" }))
: yield* _(new BarError({ randomNumber: n }))
? yield* new FooError({ message: "Oh no!" })
: yield* new BarError({ randomNumber: n })
}).pipe(
Effect.catchTags({
Foo: (error) => Effect.succeed(`Foo error: ${error.message}`),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ export class BarError {
readonly _tag = "BarError"
}

export const program = Effect.gen(function* (_) {
const n1 = yield* _(Random.next)
const n2 = yield* _(Random.next)
export const program = Effect.gen(function* () {
const n1 = yield* Random.next
const n2 = yield* Random.next

const foo = n1 > 0.5 ? "yay!" : yield* _(Effect.fail(new FooError()))
const foo = n1 > 0.5 ? "yay!" : yield* Effect.fail(new FooError())

const bar = n2 > 0.5 ? "yay!" : yield* _(Effect.fail(new BarError()))
const bar = n2 > 0.5 ? "yay!" : yield* Effect.fail(new BarError())

return foo + bar
})
Expand Down Expand Up @@ -158,10 +158,10 @@ const task3 = Console.log("Executing task3...")

// Compose the three tasks to run them in sequence.
// If one of the tasks fails, the subsequent tasks won't be executed.
const program = Effect.gen(function* (_) {
yield* _(task1)
yield* _(task2) // After task1, task2 is executed, but it fails with an error
yield* _(task3) // This computation won't be executed because the previous one fails
const program = Effect.gen(function* () {
yield* task1
yield* task2 // After task1, task2 is executed, but it fails with an error
yield* task3 // This computation won't be executed because the previous one fails
})

Effect.runPromiseExit(program).then(console.log)
Expand Down Expand Up @@ -237,8 +237,8 @@ By yielding an `Either`, we gain the ability to "pattern match" on this type to
import { Effect, Either } from "effect"
import { program } from "./error-tracking"

const recovered = Effect.gen(function* (_) {
const failureOrSuccess = yield* _(Effect.either(program))
const recovered = Effect.gen(function* () {
const failureOrSuccess = yield* Effect.either(program)
if (Either.isLeft(failureOrSuccess)) {
// failure case: you can extract the error from the `left` property
const error = failureOrSuccess.left
Expand All @@ -261,8 +261,8 @@ We can make the code less verbose by using the `Either.match` function, which di
import { Effect, Either } from "effect"
import { program } from "./error-tracking"

const recovered = Effect.gen(function* (_) {
const failureOrSuccess = yield* _(Effect.either(program))
const recovered = Effect.gen(function* () {
const failureOrSuccess = yield* Effect.either(program)
return Either.match(failureOrSuccess, {
onLeft: (error) => `Recovering from ${error._tag}`,
onRight: (value) => value // do nothing in case of success
Expand Down Expand Up @@ -304,14 +304,14 @@ Suppose we want to handle a specific error, such as `FooError`.
import { Effect, Either } from "effect"
import { program } from "./error-tracking"

const recovered = Effect.gen(function* (_) {
const failureOrSuccess = yield* _(Effect.either(program))
const recovered = Effect.gen(function* () {
const failureOrSuccess = yield* Effect.either(program)
if (Either.isLeft(failureOrSuccess)) {
const error = failureOrSuccess.left
if (error._tag === "FooError") {
return "Recovering from FooError"
}
return yield* _(Effect.fail(error))
return yield* Effect.fail(error)
} else {
return failureOrSuccess.right
}
Expand All @@ -332,8 +332,8 @@ If we also want to handle `BarError`, we can easily add another case to our code
import { Effect, Either } from "effect"
import { program } from "./error-tracking"

const recovered = Effect.gen(function* (_) {
const failureOrSuccess = yield* _(Effect.either(program))
const recovered = Effect.gen(function* () {
const failureOrSuccess = yield* Effect.either(program)
if (Either.isLeft(failureOrSuccess)) {
const error = failureOrSuccess.left
if (error._tag === "FooError") {
Expand Down
8 changes: 4 additions & 4 deletions content/docs/400-guides/150-error-management/400-fallback.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ interface Config {
const makeConfig = (/* ... */): Config => ({})

const remoteConfig = (name: string): Effect.Effect<Config, Error> =>
Effect.gen(function* (_) {
Effect.gen(function* () {
if (name === "node3") {
yield* _(Console.log(`Config for ${name} found`))
yield* Console.log(`Config for ${name} found`)
return makeConfig()
} else {
yield* _(Console.log(`Unavailable config for ${name}`))
return yield* _(Effect.fail(new Error()))
yield* Console.log(`Unavailable config for ${name}`)
return yield* Effect.fail(new Error())
}
})

Expand Down
Loading