Effect.void

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 eg
Effect.gen(function* () {
yield* Effect.void
$1
})
endsnippet

This 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);