Even though JavaScript provides built-in control flow structures, Effect offers additional control flow functions that are useful in Effect applications. In this section, we will introduce different ways to control the flow of execution.
if Expression
When working with Effect values, we can use standard JavaScript if-then-else statements:
Example (Returning None for Invalid Weight)
Here we are using the Option data type to represent the absence of a valid value.
Example (Returning Error for Invalid Weight)
You can also handle invalid inputs by using the error channel, which allows you to return an error when the input is invalid:
Conditional Operators
when
Instead of using an if (condition) expression, you can use the Effect.when function to conditionally execute an effect based on a boolean condition.
Example (Conditional Effect Execution)
In this example, the Option data type is used to represent the presence or absence of a valid value. If the condition evaluates to true (in this case, if the weight is non-negative), the effect is executed and wrapped in a Some. Otherwise, the result is None.
whenEffect
If the condition itself involves an effect, you can use Effect.whenEffect.
Example (Using an Effect as a Condition)
The following function creates a random integer, but only if a randomly generated boolean is true.
unless
The Effect.unless and Effect.unlessEffect functions are similar to the when* functions, but they are equivalent to the if (!condition) expression construct.
if
The Effect.if function allows you to execute one of two effects based on an effectful predicate. If the predicate evaluates to true, the onTrue effect is run. If it evaluates to false, the onFalse effect is run instead.
Example (Simulating a Coin Flip)
In this example, we simulate a virtual coin flip using Random.nextBoolean to generate a random boolean value. If the value is true, the onTrue effect logs “Head”. If the value is false, the onFalse effect logs “Tail”.
Zipping
zip
The Effect.zip function combines two effects into a single effect, producing a tuple with the results of both once they complete successfully.
Example (Combining Two Effects Sequentially)
Note that Effect.zip processes effects sequentially: it first completes the effect on the left and then the effect on the right.
If you want to run the effects concurrently, you can use the concurrent option:
Example (Running Effects Concurrently)
In this concurrent version, both effects run in parallel. task2 completes first, but both tasks can be logged and processed as soon as they’re done.
zipWith
The Effect.zipWith function, like Effect.zip, combines two effects. However, instead of returning a tuple with the results, it lets you apply a function to the results of the effects, producing a single value.
Example (Combining Effects with a Custom Function)
Loop Operators
loop
The Effect.loop function allows you to repeatedly update a state using a step function until a condition defined by the while function becomes false. It collects the intermediate states in an array and returns them as the final result.
Syntax
This function is similar to a while loop in JavaScript, with the addition of effectful computations:
Example (Looping with Collected Results)
In this example, the loop starts with the state 1 and continues until the state exceeds 5. Each state is incremented by 1 and is collected into an array, which becomes the final result.
The discard option
The discard option, when set to true, will discard the results of each effectful operation, returning void instead of an array.
Example (Loop with Discarded Results)
In this example, the loop performs a side effect of logging the current index on each iteration, but it discards all intermediate results. The final result is undefined.
iterate
The Effect.iterate function lets you repeatedly update a state through an effectful operation. It runs the body effect to update the state in each iteration and continues as long as the while condition evaluates to true.
Syntax
This function is similar to a while loop in JavaScript, with the addition of effectful computations:
Example (Effectful Iteration)
forEach
The Effect.forEach function allows you to iterate over an Iterable and perform an effectful operation for each element.
Syntax
It applies the specified effectful operation to each element in the Iterable. By default, the effects are executed sequentially. If you’d like to explore options for controlling concurrency, refer to the Concurrency Options documentation.
This function returns a new effect that produces an array containing the results of each individual effect.
Example (Applying Effects to Iterable Elements)
In this example, we iterate over the array [1, 2, 3, 4, 5], applying an effect that logs the current index. The Effect.as(n * 2) operation transforms each value, resulting in an array [2, 4, 6, 8, 10]. The final output is the result of collecting all the transformed values.
The discard option
The discard option, when set to true, will discard the results of each effectful operation, returning void instead of an array.
Example (Using discard to Ignore Results)
In this case, the effects still run for each element, but the results are discarded, so the final output is undefined.
all
The Effect.all function is a versatile utility that allows you to combine multiple effects into one. It works with a variety of structures, such as tuples, iterables, structs, and records.
Syntax
Where effects is a collection of effects to be combined.
By default, Effect.all runs each effect sequentially. If you’d like to explore options for running effects concurrently or with other behaviors, refer to the Concurrency Options documentation.
The final result will match the structure of the input collection of effects.
Let’s explore examples for different types of structures: tuples, iterables, objects, and records.
Example (Combining Effects in Tuples)
Example (Combining Effects in Iterables)
Example (Combining Effects in Structs)
Example (Combining Effects in Records)
Short-Circuiting Behavior
The Effect.all function stops execution on the first error it encounters, this is called short-circuiting.
If any effect in the collection fails, the remaining effects will not run, and the error will be propagated.
You can override this behavior by using the mode option.
The mode option
The { mode: "either" } option changes the behavior of Effect.all to ensure all effects run, even if some fail. Instead of stopping on the first failure, this mode collects both successes and failures, returning an array of Either instances where each result is either a Right (success) or a Left (failure).
Example (mode: "either")
Similarly, the { mode: "validate" } option uses Option to indicate success or failure. Each effect returns None for success and Some with the error for failure.