Guidelines

On this page

Using runMain

In Effect, runMain serves as the primary entry point for running an Effect application on Node:

ts
import { Effect, Console, Schedule, pipe } from "effect"
import { NodeRuntime } from "@effect/platform-node"
const program = pipe(
Effect.addFinalizer(() => Console.log("Application is about to exit!")),
Effect.andThen(Console.log("Application started!")),
Effect.andThen(
Effect.repeat(Console.log("still alive..."), {
schedule: Schedule.spaced("1 seconds")
})
),
Effect.scoped
)
// Effect.runFork(program) // no graceful teardown with CTRL+C
NodeRuntime.runMain(program) // graceful teardown with CTRL+C
ts
import { Effect, Console, Schedule, pipe } from "effect"
import { NodeRuntime } from "@effect/platform-node"
const program = pipe(
Effect.addFinalizer(() => Console.log("Application is about to exit!")),
Effect.andThen(Console.log("Application started!")),
Effect.andThen(
Effect.repeat(Console.log("still alive..."), {
schedule: Schedule.spaced("1 seconds")
})
),
Effect.scoped
)
// Effect.runFork(program) // no graceful teardown with CTRL+C
NodeRuntime.runMain(program) // graceful teardown with CTRL+C

The runMain function is responsible for finding all fibers and interrupting them. Internally, it adds an observer for the fiber by listening to sigint and interrupts all fibers.

It's important to note that teardown should be on the main effect. If you kill the fiber that runs the application/server, the teardown of everything will occur. This is precisely what runMain from the platform-node package does.

Avoid Tacit usage

Avoid tacit function calls like map(f) and using flow

In Effect, it's recommended not to use functions point-free, meaning avoiding tacit usage.

While you're free to use tacit functions if you prefer, it's important to know that it can cause issues. It's safer to use (x) => fn(x) instead.

Using functions tacitly, especially with optional parameters, can be unsafe. If a function has overloads, using it tacitly might erase all generics, leading to bugs. For example, check out this X thread: link to thread.

TypeScript inference can also be compromised when using tacit functions, which can lead to unexpected errors. So, it's not just a matter of style; it's a protective measure to avoid mistakes.

Additionally, stack traces may not be as clear when tacit usage is involved. It's risky without much benefit, as TypeScript may not properly check arguments, especially with optional ones, leading to potential issues.

It's worth trying without tacit usage, especially when dealing with generic functions with overloads, as using them tacitly can result in losing the generics.