Suppose you want to create a custom function elapsed that prints the elapsed time taken by an effect to execute.
Using plain pipe
Initially, you may come up with code that uses the standard pipemethod, but this approach can lead to excessive nesting and result in verbose and hard-to-read code:
Example (Measuring Elapsed Time with pipe)
To address this issue and make the code more manageable, there is a solution: the “do simulation.”
Using 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:
Start the do simulation using the Effect.Do value:
constprogram= Effect.Do.pipe(/* ... rest of the code */)
Within the do simulation scope, you can use the Effect.bind function to define variables and bind them to Effect values:
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.
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 function like this:
Example (Using Do Simulation to Measure Elapsed Time)
Creates an Effect that represents a synchronous side-effectful computation.
When to Use
Use sync when you are sure the operation will not fail.
Details
The provided function (thunk) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
@see ― try_try for a version that can handle failures.
@example
// Title: Logging a Message
import { Effect } from"effect"
constlog= (message:string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
constprogram=log("Hello, World!")
@since ― 2.0.0
sync(() =>new
var Date:DateConstructor
new () =>Date (+3 overloads)
Date().
Date.getTime(): number
Returns the stored time value in milliseconds since midnight, January 1, 1970 UTC.
getTime())
5
6
const
constelapsed: <R, E, A>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R>
elapsed= <
function (typeparameter) Rin <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
R,
function (typeparameter) Ein <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
E,
function (typeparameter) Ain <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
A>(
7
self: Effect.Effect<A, E, R>
self:
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
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.
@since ― 2.0.0
@since ― 2.0.0
Effect<
function (typeparameter) Ain <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
A,
function (typeparameter) Ein <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
E,
function (typeparameter) Rin <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
R>
8
):
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
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.
@since ― 2.0.0
@since ― 2.0.0
Effect<
function (typeparameter) Ain <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
A,
function (typeparameter) Ein <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
E,
function (typeparameter) Rin <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
R> =>
9
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constDo: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:
Start the do simulation using the Do value
Within the do simulation scope, you can use the bind function to define variables and bind them to Effect values
You can accumulate multiple bind statements to define multiple variables within the scope
Inside the do simulation scope, you can also use the let function to define variables and bind them to simple values
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:
Start the do simulation using the Do value
Within the do simulation scope, you can use the bind function to define variables and bind them to Effect values
You can accumulate multiple bind statements to define multiple variables within the scope
Inside the do simulation scope, you can also use the let function to define variables and bind them to simple values
}) =>Effect.Effect<A, E, R>) => <E1, R1>(self:Effect.Effect<{
startMillis:number;
}, E1, R1>) =>Effect.Effect<...> (+1overload)
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:
Start the do simulation using the Do value
Within the do simulation scope, you can use the bind function to define variables and bind them to Effect values
You can accumulate multiple bind statements to define multiple variables within the scope
Inside the do simulation scope, you can also use the let function to define variables and bind them to simple values
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:
Start the do simulation using the Do value
Within the do simulation scope, you can use the bind function to define variables and bind them to Effect values
You can accumulate multiple bind statements to define multiple variables within the scope
Inside the do simulation scope, you can also use the let function to define variables and bind them to simple values
Runs a side effect with the result of an effect without changing the original
value.
When to Use
Use tap when you want to perform a side effect, like logging or tracking,
without modifying the main value. This is useful when you need to observe or
record an action but want the original value to be passed to the next step.
Details
tap works similarly to flatMap, but it ignores the result of the function
passed to it. The value from the previous effect remains available for the
next part of the chain. Note that if the side effect fails, the entire chain
will fail too.
@example
// Title: Logging a step in a pipeline
import { Console, Effect, pipe } from"effect"
// Function to apply a discount safely to a transaction amount
constapplyDiscount= (
total:number,
discountRate:number
):Effect.Effect<number, Error> =>
discountRate ===0
? Effect.fail(newError("Discount rate cannot be zero"))
map takes a function and applies it to the value contained within an
effect, creating a new effect with the transformed value.
It's important to note that effects are immutable, meaning that the original
effect is not modified. Instead, a new effect is returned with the updated
value.
@see ― mapError for a version that operates on the error channel.
@see ― mapBoth for a version that operates on both channels.
@see ― flatMap or andThen for a version that can return a new effect.
Executes an effect and returns the result as a Promise.
When to Use
Use runPromise when you need to execute an effect and work with the
result using Promise syntax, typically for compatibility with other
promise-based code.
If the effect succeeds, the promise will resolve with the result. If the
effect fails, the promise will reject with an error.
@see ― runPromiseExit for a version that returns an Exit type instead of rejecting.
@example
// Title: Running a Successful Effect as a Promise
Attaches callbacks for the resolution and/or rejection of the Promise.
@param ― onfulfilled The callback to execute when the Promise is resolved.
@param ― onrejected The callback to execute when the Promise is rejected.
@returns ― A Promise for the completion of which ever callback is executed.
then(
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 and
process.stderr. 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 for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('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
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=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(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
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)
(the arguments are all passed to util.format()).
The most concise and convenient solution is to use Effect.gen, 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.
Example (Using Effect.gen to Measure Elapsed Time)
Creates an Effect that represents a synchronous side-effectful computation.
When to Use
Use sync when you are sure the operation will not fail.
Details
The provided function (thunk) must not throw errors; if it does, the error
will be treated as a "defect".
This defect is not a standard error but indicates a flaw in the logic that
was expected to be error-free. You can think of it similar to an unexpected
crash in the program, which can be further managed or logged using tools like
catchAllDefect
.
@see ― try_try for a version that can handle failures.
@example
// Title: Logging a Message
import { Effect } from"effect"
constlog= (message:string) =>
Effect.sync(() => {
console.log(message) // side effect
})
// ┌─── Effect<void, never, never>
// ▼
constprogram=log("Hello, World!")
@since ― 2.0.0
sync(() =>new
var Date:DateConstructor
new () =>Date (+3 overloads)
Date().
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
constelapsed: <R, E, A>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R>
elapsed= <
function (typeparameter) Rin <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
R,
function (typeparameter) Ein <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
E,
function (typeparameter) Ain <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
A>(
8
self: Effect.Effect<A, E, R>
self:
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
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.
@since ― 2.0.0
@since ― 2.0.0
Effect<
function (typeparameter) Ain <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
A,
function (typeparameter) Ein <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
E,
function (typeparameter) Rin <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
R>
9
):
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
interfaceEffect<outA, outE=never, outR=never>
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.
@since ― 2.0.0
@since ― 2.0.0
Effect<
function (typeparameter) Ain <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
A,
function (typeparameter) Ein <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
E,
function (typeparameter) Rin <R, E, A>(self:Effect.Effect<A, E, R>):Effect.Effect<A, E, R>
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
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 and
process.stderr. 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 for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('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
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=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(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
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)
(the arguments are all passed to util.format()).
Executes an effect and returns the result as a Promise.
When to Use
Use runPromise when you need to execute an effect and work with the
result using Promise syntax, typically for compatibility with other
promise-based code.
If the effect succeeds, the promise will resolve with the result. If the
effect fails, the promise will reject with an error.
@see ― runPromiseExit for a version that returns an Exit type instead of rejecting.
@example
// Title: Running a Successful Effect as a Promise
Attaches callbacks for the resolution and/or rejection of the Promise.
@param ― onfulfilled The callback to execute when the Promise is resolved.
@param ― onrejected The callback to execute when the Promise is rejected.
@returns ― A Promise for the completion of which ever callback is executed.
then(
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 and
process.stderr. 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 for
more information.
Example using the global console:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(newError('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
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=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(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
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)
(the arguments are all passed to util.format()).
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.