Effect.void

Much like Context.empty (22) or Layer.empty (34) , 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);