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:
Example (Measuring Elapsed Time with pipe
)
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:
-
Start the do simulation using the
Effect.Do
value: -
Within the do simulation scope, you can use the
Effect.bind
function to define variables and bind them toEffect
values:variableName
is the name you choose for the variable you want to define. It must be unique within the scope.effectValue
is theEffect
value that you want to bind to the variable. It can be the result of a function call or any other validEffect
value.
-
You can accumulate multiple
Effect.bind
statements to define multiple variables within the scope: -
Inside the do simulation scope, you can also use the
Effect.let
function to define variables and bind them to simple 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 anumber
,string
, orboolean
.
-
Regular Effect functions like
Effect.andThen
,Effect.flatMap
,Effect.tap
, andEffect.map
can still be used within the do simulation. These functions will receive the accumulated variables as arguments within the scope:
With the do simulation, you can rewrite the elapsed
function like this:
Example (Using Do Simulation to Measure Elapsed Time)
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)
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.