Much like Context.empty (22)
or Layer.empty (35)
, Effect.void is an “empty” Effect that does nothing. On its own, it is not very useful.
// ┌─── Effect.Effect<void>// ▼const toVoid = Effect.succeed(3).pipe( Effect.flatMap((x: number) => Effect.void));
// ┌─── Effect.Effect<number>// ▼const fromVoid = Effect.void.pipe( Effect.flatMap((x: void) => Effect.succeed(3)));There are two main situations where Effect.void can be very useful. The first is as a “base” or “fallback” value for some recursive operation, like we saw with Context.empty (22)
. For example, here is a combinator that runs many effects in sequence, stopping after the first failure.
const allSucceed = <E>( ...effects: ReadonlyArray<Effect.Effect<void, E>>): Effect.Effect<void, E> => effects.reduce((acc, curr) => Effect.zipRight(acc, curr), Effect.void);
// ┌─── Effect.Effect<void, string, never>// ▼const nineThousand = allSucceed( Effect.succeed(3000), Effect.succeed(6000), Effect.succeed(9000));
const fails = allSucceed( Effect.succeed(1), Effect.fail("uh oh"), Effect.fail("never runs"));This is fairly rare in practice, as other default values might be more useful. For example, you might prefer Effect.succeed(undefined), or more commonly Effect.succeedNone, which we will meet in a future lesson.
The more common usage of Effect.void is as a placeholder. For example, I have this set up in a snippet:
snippet egEffect.gen(function* () { yield* Effect.void $1})endsnippetThis helps me start writing Effect generator logic without immediately seeing red squigglies from an over-eager “generator does not use yield” linting rule.
We can implement Effect.void as a simple constant.
import { Effect } from "effect";
type Void = Effect.Effect<void>;
const void_: Void = Effect.succeed(undefined);