Skip to content

Simplifying Excessive Nesting

Suppose you want to create a custom function elapsed that prints the elapsed time taken by an effect to execute.

Initially, you may come up with code that uses the standard pipe method, but this approach can lead to excessive nesting and result in verbose and hard-to-read code:

1
import {
import Effect
Effect
,
import Console
Console
} from "effect"
2
3
// Get the current timestamp
4
const
const now: Effect.Effect<number, never, never>
now
=
import Effect
Effect
.
const sync: <number>(evaluate: LazyArg<number>) => Effect.Effect<number, never, never>
sync
(() => new
var Date: DateConstructor new () => Date (+3 overloads)
Date
().
(method) Date.getTime(): number

Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC.

getTime
())
5
6
// Prints the elapsed time occurred to `self` to execute
7
const
const elapsed: <R, E, A>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
elapsed
= <
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
>(
8
(parameter) self: Effect.Effect<A, E, R>
self
:
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
>
9
):
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
> =>
10
const now: Effect.Effect<number, never, never>
now
.
(method) Pipeable.pipe<Effect.Effect<number, never, never>, Effect.Effect<NoInfer<A>, E, R>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<number, never, never>) => Effect.Effect<NoInfer<A>, E, R>): Effect.Effect<...> (+21 overloads)
pipe
(
11
import Effect
Effect
.
const andThen: <number, Effect.Effect<NoInfer<A>, E, R>>(f: (a: number) => Effect.Effect<NoInfer<A>, E, R>) => <E, R>(self: Effect.Effect<number, E, R>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) startMillis: number
startMillis
) =>
12
(parameter) self: Effect.Effect<A, E, R>
self
.
(method) Pipeable.pipe<Effect.Effect<A, E, R>, Effect.Effect<NoInfer<A>, E, R>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<A, E, R>) => Effect.Effect<NoInfer<A>, E, R>): Effect.Effect<...> (+21 overloads)
pipe
(
13
import Effect
Effect
.
const andThen: <A, Effect.Effect<NoInfer<A>, never, never>>(f: (a: NoInfer<A>) => Effect.Effect<NoInfer<A>, never, never>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) result: NoInfer<A>
result
) =>
14
const now: Effect.Effect<number, never, never>
now
.
(method) Pipeable.pipe<Effect.Effect<number, never, never>, Effect.Effect<NoInfer<A>, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<number, never, never>) => Effect.Effect<NoInfer<A>, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
15
import Effect
Effect
.
const andThen: <number, Effect.Effect<NoInfer<A>, never, never>>(f: (a: number) => Effect.Effect<NoInfer<A>, never, never>) => <E, R>(self: Effect.Effect<number, E, R>) => Effect.Effect<...> (+3 overloads)

Executes a sequence of two actions, typically two `Effect`s, where the second action can depend on the result of the first action. The `that` action can take various forms: - a value - a function returning a value - a promise - a function returning a promise - an effect - a function returning an effect

andThen
((
(parameter) endMillis: number
endMillis
) => {
16
// Calculate the elapsed time in milliseconds
17
const
const elapsed: number
elapsed
=
(parameter) endMillis: number
endMillis
-
(parameter) startMillis: number
startMillis
18
// Log the elapsed time
19
return
import Console
Console
.
const log: (...args: ReadonlyArray<any>) => Effect.Effect<void>
log
(`Elapsed: ${
const elapsed: number
elapsed
}`).
(method) Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<NoInfer<A>, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<NoInfer<A>, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
20
import Effect
Effect
.
const map: <void, NoInfer<A>>(f: (a: void) => NoInfer<A>) => <E, R>(self: Effect.Effect<void, E, R>) => Effect.Effect<NoInfer<A>, E, R> (+1 overload)
map
(() =>
(parameter) result: NoInfer<A>
result
)
21
)
22
})
23
)
24
)
25
)
26
)
27
)
28
29
// Simulates a successful computation with a delay of 200 milliseconds
30
const
const task: Effect.Effect<string, never, never>
task
=
import Effect
Effect
.
const succeed: <string>(value: string) => Effect.Effect<string, never, never>
succeed
("some task").
(method) Pipeable.pipe<Effect.Effect<string, never, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<string, never, never>) => Effect.Effect<string, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified `Duration`.

delay
("200 millis"))
31
32
const
const program: Effect.Effect<string, never, never>
program
=
const elapsed: <never, never, string>(self: Effect.Effect<string, never, never>) => Effect.Effect<string, never, never>
elapsed
(
const task: Effect.Effect<string, never, never>
task
)
33
34
import Effect
Effect
.
const runPromise: <string, never>(effect: Effect.Effect<string, never, never>, options?: { readonly signal?: AbortSignal; } | undefined) => Promise<string>

Runs an `Effect` workflow, returning a `Promise` which resolves with the result of the workflow or rejects with an error.

runPromise
(
const program: Effect.Effect<string, never, never>
program
).
(method) Promise<string>.then<void, never>(onfulfilled?: ((value: string) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
namespace console var console: Console

The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```

console
.
(method) globalThis.Console.log(message?: any, ...optionalParams: any[]): void

Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.

log
)
35
/*
36
Output:
37
Elapsed: 204
38
some task
39
*/

To address this issue and make the code more manageable, there is a solution: the “do simulation.”

The “do simulation” in Effect allows you to write code in a more declarative style, similar to the “do notation” in other programming languages. It provides a way to define variables and perform operations on them using functions like Effect.bind and Effect.let.

Here’s how the do simulation works:

  1. Start the do simulation using the Effect.Do value:

    const program = Effect.Do.pipe(/* ... rest of the code */)
  2. Within the do simulation scope, you can use the Effect.bind function to define variables and bind them to Effect values:

    Effect.bind("variableName", (scope) => effectValue)
    • variableName is the name you choose for the variable you want to define. It must be unique within the scope.
    • effectValue is the Effect value that you want to bind to the variable. It can be the result of a function call or any other valid Effect value.
  3. You can accumulate multiple Effect.bind statements to define multiple variables within the scope:

    Effect.bind("variable1", () => effectValue1),
    Effect.bind("variable2", ({ variable1 }) => effectValue2),
    // ... additional bind statements
  4. Inside the do simulation scope, you can also use the Effect.let function to define variables and bind them to simple values:

    Effect.let("variableName", (scope) => simpleValue)
    • variableName is the name you give to the variable. Like before, it must be unique within the scope.
    • simpleValue is the value you want to assign to the variable. It can be a simple value like a number, string, or boolean.
  5. Regular Effect functions like Effect.andThen, Effect.flatMap, Effect.tap, and Effect.map can still be used within the do simulation. These functions will receive the accumulated variables as arguments within the scope:

    Effect.andThen(({ variable1, variable2 }) => {
    // Perform operations using variable1 and variable2
    // Return an `Effect` value as the result
    })

With the do simulation, you can rewrite the elapsed combinator like this:

1
import {
import Effect
Effect
,
import Console
Console
} from "effect"
2
3
// Get the current timestamp
4
const
const now: Effect.Effect<number, never, never>
now
=
import Effect
Effect
.
const sync: <number>(evaluate: LazyArg<number>) => Effect.Effect<number, never, never>
sync
(() => new
var Date: DateConstructor new () => Date (+3 overloads)
Date
().
(method) Date.getTime(): number

Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC.

getTime
())
5
6
const
const elapsed: <R, E, A>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
elapsed
= <
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
>(
7
(parameter) self: Effect.Effect<A, E, R>
self
:
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
>
8
):
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
> =>
9
import Effect
Effect
.
const Do: Effect.Effect<{}, never, never>

The "do simulation" in Effect allows you to write code in a more declarative style, similar to the "do notation" in other programming languages. It provides a way to define variables and perform operations on them using functions like `bind` and `let`. Here's how the do simulation works: 1. Start the do simulation using the `Do` value 2. Within the do simulation scope, you can use the `bind` function to define variables and bind them to `Effect` values 3. You can accumulate multiple `bind` statements to define multiple variables within the scope 4. Inside the do simulation scope, you can also use the `let` function to define variables and bind them to simple values

Do
.
(method) Pipeable.pipe<Effect.Effect<{}, never, never>, Effect.Effect<{ startMillis: number; }, never, never>, Effect.Effect<{ startMillis: number; result: A; }, E, R>, Effect.Effect<...>, Effect.Effect<...>, Effect.Effect<...>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>, cd: (_: Effect.Effect<...>) => Effect.Effect<...>, de: (_: Effect.Effect<...>) => Effect.Effect<...>, ef: (_: Effect.Effect<...>) => Effect.Effect<...>, fg: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
10
import Effect
Effect
.
const bind: <"startMillis", {}, number, never, never>(name: "startMillis", f: (a: {}) => Effect.Effect<number, never, never>) => <E1, R1>(self: Effect.Effect<{}, E1, R1>) => Effect.Effect<...> (+1 overload)

The "do simulation" in Effect allows you to write code in a more declarative style, similar to the "do notation" in other programming languages. It provides a way to define variables and perform operations on them using functions like `bind` and `let`. Here's how the do simulation works: 1. Start the do simulation using the `Do` value 2. Within the do simulation scope, you can use the `bind` function to define variables and bind them to `Effect` values 3. You can accumulate multiple `bind` statements to define multiple variables within the scope 4. Inside the do simulation scope, you can also use the `let` function to define variables and bind them to simple values

bind
("startMillis", () =>
const now: Effect.Effect<number, never, never>
now
),
11
import Effect
Effect
.
const bind: <"result", { startMillis: number; }, A, E, R>(name: "result", f: (a: { startMillis: number; }) => Effect.Effect<A, E, R>) => <E1, R1>(self: Effect.Effect<{ startMillis: number; }, E1, R1>) => Effect.Effect<...> (+1 overload)

The "do simulation" in Effect allows you to write code in a more declarative style, similar to the "do notation" in other programming languages. It provides a way to define variables and perform operations on them using functions like `bind` and `let`. Here's how the do simulation works: 1. Start the do simulation using the `Do` value 2. Within the do simulation scope, you can use the `bind` function to define variables and bind them to `Effect` values 3. You can accumulate multiple `bind` statements to define multiple variables within the scope 4. Inside the do simulation scope, you can also use the `let` function to define variables and bind them to simple values

bind
("result", () =>
(parameter) self: Effect.Effect<A, E, R>
self
),
12
import Effect
Effect
.
const bind: <"endMillis", { startMillis: number; result: A; }, number, never, never>(name: "endMillis", f: (a: { startMillis: number; result: A; }) => Effect.Effect<number, never, never>) => <E1, R1>(self: Effect.Effect<...>) => Effect.Effect<...> (+1 overload)

The "do simulation" in Effect allows you to write code in a more declarative style, similar to the "do notation" in other programming languages. It provides a way to define variables and perform operations on them using functions like `bind` and `let`. Here's how the do simulation works: 1. Start the do simulation using the `Do` value 2. Within the do simulation scope, you can use the `bind` function to define variables and bind them to `Effect` values 3. You can accumulate multiple `bind` statements to define multiple variables within the scope 4. Inside the do simulation scope, you can also use the `let` function to define variables and bind them to simple values

bind
("endMillis", () =>
const now: Effect.Effect<number, never, never>
now
),
13
import Effect
Effect
.
(alias) let<"elapsed", { startMillis: number; result: A; endMillis: number; }, number>(name: "elapsed", f: (a: { startMillis: number; result: A; endMillis: number; }) => number): <E, R>(self: Effect.Effect<{ startMillis: number; result: A; endMillis: number; }, E, R>) => Effect.Effect<...> (+1 overload) export let
let
(
14
"elapsed",
15
({
(parameter) startMillis: number
startMillis
,
(parameter) endMillis: number
endMillis
}) =>
(parameter) endMillis: number
endMillis
-
(parameter) startMillis: number
startMillis
// Calculate the elapsed time in milliseconds
16
),
17
import Effect
Effect
.
const tap: <{ startMillis: number; result: A; endMillis: number; elapsed: number; }, Effect.Effect<void, never, never>>(f: (a: { startMillis: number; result: A; endMillis: number; elapsed: number; }) => Effect.Effect<...>) => <E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+7 overloads)
tap
(({
(parameter) elapsed: number
elapsed
}) =>
import Console
Console
.
const log: (...args: ReadonlyArray<any>) => Effect.Effect<void>
log
(`Elapsed: ${
(parameter) elapsed: number
elapsed
}`)), // Log the elapsed time
18
import Effect
Effect
.
const map: <{ startMillis: number; result: A; endMillis: number; elapsed: number; }, A>(f: (a: { startMillis: number; result: A; endMillis: number; elapsed: number; }) => A) => <E, R>(self: Effect.Effect<{ startMillis: number; result: A; endMillis: number; elapsed: number; }, E, R>) => Effect.Effect<...> (+1 overload)
map
(({
(parameter) result: A
result
}) =>
(parameter) result: A
result
)
19
)
20
21
// Simulates a successful computation with a delay of 200 milliseconds
22
const
const task: Effect.Effect<string, never, never>
task
=
import Effect
Effect
.
const succeed: <string>(value: string) => Effect.Effect<string, never, never>
succeed
("some task").
(method) Pipeable.pipe<Effect.Effect<string, never, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<string, never, never>) => Effect.Effect<string, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified `Duration`.

delay
("200 millis"))
23
24
const
const program: Effect.Effect<string, never, never>
program
=
const elapsed: <never, never, string>(self: Effect.Effect<string, never, never>) => Effect.Effect<string, never, never>
elapsed
(
const task: Effect.Effect<string, never, never>
task
)
25
26
import Effect
Effect
.
const runPromise: <string, never>(effect: Effect.Effect<string, never, never>, options?: { readonly signal?: AbortSignal; } | undefined) => Promise<string>

Runs an `Effect` workflow, returning a `Promise` which resolves with the result of the workflow or rejects with an error.

runPromise
(
const program: Effect.Effect<string, never, never>
program
).
(method) Promise<string>.then<void, never>(onfulfilled?: ((value: string) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
namespace console var console: Console

The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```

console
.
(method) globalThis.Console.log(message?: any, ...optionalParams: any[]): void

Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.

log
)
27
/*
28
Output:
29
Elapsed: 204
30
some task
31
*/

In this solution, we use the do simulation to simplify the code. The elapsed function now starts with Effect.Do to enter the simulation scope. Inside the scope, we use Effect.bind to define variables and bind them to the corresponding effects.

The most concise and convenient solution is to use the Effect.gen constructor, which allows you to work with generators when dealing with effects. This approach leverages the native scope provided by the generator syntax, avoiding excessive nesting and leading to more concise code.

1
import {
import Effect
Effect
} from "effect"
2
3
// Get the current timestamp
4
const
const now: Effect.Effect<number, never, never>
now
=
import Effect
Effect
.
const sync: <number>(evaluate: LazyArg<number>) => Effect.Effect<number, never, never>
sync
(() => new
var Date: DateConstructor new () => Date (+3 overloads)
Date
().
(method) Date.getTime(): number

Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC.

getTime
())
5
6
// Prints the elapsed time occurred to `self` to execute
7
const
const elapsed: <R, E, A>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
elapsed
= <
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
>(
8
(parameter) self: Effect.Effect<A, E, R>
self
:
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
>
9
):
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never> namespace Effect

The `Effect` interface defines a value that lazily describes a workflow or job. The workflow requires some context `R`, and may fail with an error of type `E`, or succeed with a value of type `A`. `Effect` values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability. To run an `Effect` value, you need a `Runtime`, which is a type that is capable of executing `Effect` values.

Effect
<
(type parameter) A in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
A
,
(type parameter) E in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
E
,
(type parameter) R in <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
R
> =>
10
import Effect
Effect
.
const gen: <YieldWrap<Effect.Effect<A, E, R>> | YieldWrap<Effect.Effect<number, never, never>>, A>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)
gen
(function* () {
11
const
const startMillis: number
startMillis
= yield*
const now: Effect.Effect<number, never, never>
now
12
const
const result: A
result
= yield*
(parameter) self: Effect.Effect<A, E, R>
self
13
const
const endMillis: number
endMillis
= yield*
const now: Effect.Effect<number, never, never>
now
14
// Calculate the elapsed time in milliseconds
15
const
const elapsed: number
elapsed
=
const endMillis: number
endMillis
-
const startMillis: number
startMillis
16
// Log the elapsed time
17
namespace console var console: Console

The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```

console
.
(method) Console.log(message?: any, ...optionalParams: any[]): void

Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.

log
(`Elapsed: ${
const elapsed: number
elapsed
}`)
18
return
const result: A
result
19
})
20
21
// Simulates a successful computation with a delay of 200 milliseconds
22
const
const task: Effect.Effect<string, never, never>
task
=
import Effect
Effect
.
const succeed: <string>(value: string) => Effect.Effect<string, never, never>
succeed
("some task").
(method) Pipeable.pipe<Effect.Effect<string, never, never>, Effect.Effect<string, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<string, never, never>) => Effect.Effect<string, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified `Duration`.

delay
("200 millis"))
23
24
const
const program: Effect.Effect<string, never, never>
program
=
const elapsed: <never, never, string>(self: Effect.Effect<string, never, never>) => Effect.Effect<string, never, never>
elapsed
(
const task: Effect.Effect<string, never, never>
task
)
25
26
import Effect
Effect
.
const runPromise: <string, never>(effect: Effect.Effect<string, never, never>, options?: { readonly signal?: AbortSignal; } | undefined) => Promise<string>

Runs an `Effect` workflow, returning a `Promise` which resolves with the result of the workflow or rejects with an error.

runPromise
(
const program: Effect.Effect<string, never, never>
program
).
(method) Promise<string>.then<void, never>(onfulfilled?: ((value: string) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

then
(
namespace console var console: Console

The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v22.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v22.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```

console
.
(method) Console.log(message?: any, ...optionalParams: any[]): void

Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v22.x/api/util.html#utilformatformat-args) for more information.

log
)
27
/*
28
Output:
29
Elapsed: 204
30
some task
31
*/

In this solution, we switch to using generators to simplify the code. The elapsed function now uses a generator function (Effect.gen) to define the flow of execution. Within the generator, we use yield* to invoke effects and bind their results to variables. This eliminates the nesting and provides a more readable and sequential code structure.

The generator style in Effect uses a more linear and sequential flow of execution, resembling traditional imperative programming languages. This makes the code easier to read and understand, especially for developers who are more familiar with imperative programming paradigms.

On the other hand, the pipe style can lead to excessive nesting, especially when dealing with complex effectful computations. This can make the code harder to follow and debug.

For more information on how to use generators in Effect, you can refer to the Using Generators in Effect guide.