# Guidelines

Most Effect code follows a handful of conventions that keep it readable, traceable,
and testable. This page walks through the ones that matter most day to day: how
to write generators, when to wrap them in `Effect.fn`, why `async`/`await` and
`try`/`catch` have no place inside an effect, why behavior belongs in services
rather than module-level globals, and why you read the time from `Clock` instead
of `Date`.

```ts
import { Clock, Context, Effect, Layer, Schema } from "effect"

// Errors are schema-tagged classes. `Schema.Defect` captures an unknown cause.
class ChargeError extends Schema.TaggedErrorClass<ChargeError>()("ChargeError", {
  cause: Schema.Defect
}) {}

// Behavior lives in a service, not in free functions reaching for globals.
class Payments extends Context.Service<Payments, {
  readonly charge: (cents: number) => Effect.Effect<string, ChargeError>
}>()("app/Payments") {
  static readonly layer = Layer.effect(
    Payments,
    Effect.gen(function*() {
      // A function that returns an Effect is written with Effect.fn, not a
      // plain arrow that returns Effect.gen. The name feeds stack traces and
      // opens a tracing span automatically.
      const charge = Effect.fn("Payments.charge")(function*(cents: number) {
        // Read the time from Clock — never `Date.now()` — so tests can control it.
        const now = yield* Clock.currentTimeMillis
        yield* Effect.log(`Charging ${cents} cents at ${now}`)
        // `return yield*` makes the failure terminal and keeps the types honest.
        if (cents <= 0) {
          return yield* new ChargeError({ cause: "amount must be positive" })
        }
        return `txn_${now}`
      })

      return Payments.of({ charge })
    })
  )
}
```

The rest of this page explains each of these choices.

## `Effect.gen` for imperative composition

`Effect.gen` lets you write effectful code in a familiar, imperative style. Each
`yield*` runs an effect and unwraps its success value, much like `await` unwraps a
promise — but with full error and requirement tracking in the type.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  yield* Effect.log("Starting...")
  const a = yield* Effect.succeed(1)
  const b = yield* Effect.succeed(2)
  return a + b
})
```

Use `Effect.gen` for inline composition — one-off pipelines, the body of a layer,
or steps that won't be reused on their own.
**Always `return yield*` terminal effects:** When you yield an effect that ends the computation (`Effect.fail`, a tagged
error, `Effect.interrupt`), prefix it with `return`. Without it, TypeScript
believes execution continues and any code after becomes silently unreachable.

```ts
// ❌ No return: the type-checker thinks the function keeps going
if (invalid) {
  yield* new ValidationError({ cause: "bad input" })
}
// ✅ Terminal and explicit
if (invalid) {
  return yield* new ValidationError({ cause: "bad input" })
}
```

## `Effect.fn` for functions that return an effect

When you write a *named function* whose job is to produce an effect, wrap the
generator with `Effect.fn(name)` instead of returning a bare `Effect.gen`.

```ts
import { Effect } from "effect"

// The name improves stack traces and attaches a span (via Effect.withSpan).
const greet = Effect.fn("greet")(function*(name: string) {
  yield* Effect.log(`Hello, ${name}`)
  return name.length
})
```

  ```ts
import { Effect } from "effect"

// No name, no span, worse stack traces. Avoid this shape.
const greet = (name: string) =>
  Effect.gen(function*() {
    yield* Effect.log(`Hello, ${name}`)
    return name.length
  })
```

  You add extra behavior by passing further arguments to `Effect.fn` — do **not**
use `.pipe` on the result:

```ts
import { Effect } from "effect"

const greet = Effect.fn("greet")(
  function*(name: string) {
    return name.length
  },
  // Each additional argument is applied like a pipe step.
  Effect.annotateLogs({ method: "greet" })
)
```
**Hot paths:** `Effect.fn` attaches a tracing span on every call. In a performance-critical
inner loop where you do not want that overhead, use `Effect.fnUntraced`, which
has the same shape but skips the span.

When you write inline `Effect.gen` (not wrapped in a function), add spans and
adjustments with `.pipe` as usual:

```ts
import { Effect } from "effect"

Effect.gen(function*() {
  yield* Effect.log("working")
}).pipe(Effect.withSpan("work"))
```

## No `async`/`await` or `try`/`catch` inside effects

Effect models concurrency and failure in the type system. Mixing in JavaScript's
own mechanisms breaks that model.

`try`/`catch` does not work inside `Effect.gen`: a failed effect short-circuits the
generator rather than throwing a catchable exception, so the `catch` block never
runs. Use Effect's error handling instead.

```ts
import { Effect } from "effect"

declare const risky: Effect.Effect<number, Error>

// ❌ The catch never fires — Effect failures aren't thrown exceptions.
// Effect.gen(function*() {
//   try {
//     return yield* risky
//   } catch (e) {
//     return 0
//   }
// })

// ✅ Inspect the outcome as a value with Effect.result...
const handled = Effect.gen(function*() {
  const result = yield* Effect.result(risky)
  return result._tag === "Success" ? result.success : 0
})

// ✅ ...or recover with a combinator. `Effect.catch` is the catch-all.
const recovered = risky.pipe(Effect.catch(() => Effect.succeed(0)))
```

Likewise, never reach for `async`/`await`. To lift a promise into Effect, use
`Effect.promise` (for promises that cannot reject meaningfully) or
`Effect.tryPromise` (to capture a rejection as a typed error):

```ts
import { Effect } from "effect"

declare function loadUser(id: string): Promise<{ name: string }>

const user = Effect.tryPromise({
  try: () => loadUser("u_1"),
  catch: (cause) => new Error(`failed to load user: ${cause}`)
})
```
**Note:** The related Effect combinators — `Effect.catch` (catch-all), `Effect.catchTag` /
`Effect.catchTags` (by error tag), and `Effect.catchCause` — replace every use of
`try`/`catch`. See [Error Management](https://effect.plants.sh/error-management/) for the full set.

## Services over module-level globals

Anything your code depends on — a database handle, an HTTP client, configuration,
even the current time — should be reached through a service rather than a global
singleton imported at the top of a module. Globals are invisible to the type
system, impossible to swap in tests, and tie initialization order to class Config extends Context.Service<Config, {
  readonly apiUrl: string
}>()("app/Config") {
  static readonly layer = Layer.succeed(Config, Config.of({
    apiUrl: "https://api.example.com"
  }))
}

// The dependency shows up in the Effect's requirements (`R`) channel, so the
// compiler forces you to provide it — and lets a test provide a fake.
const fetchUrl = Effect.fn("fetchUrl")(function*(path: string) {
  const config = yield* Config
  return `${config.apiUrl}${path}`
})
```

For configuration values, prefer Effect's [Configuration](https://effect.plants.sh/configuration/) layer;
for singletons with a sensible default that callers may override, use
`Context.Reference`. See [Services & Layers](https://effect.plants.sh/services-and-layers/) for the
complete pattern.

## `Clock` over `Date`

Reading the wall clock directly (`Date.now()`, `new Date()`) makes code
non-deterministic and untestable: every run produces different timestamps and you
cannot fast-forward time. Effect exposes the clock as a service so tests can
substitute a `TestClock`.

```ts
const stampedEvent = Effect.fn("stampedEvent")(function*(message: string) {
  // Pulls from the ambient Clock service, controllable in tests.
  const millis = yield* Clock.currentTimeMillis
  return { message, at: millis }
})
```

In tests, `TestClock` lets you advance time explicitly so time-dependent logic —
timeouts, retries, schedules — runs instantly and deterministically. See
[Testing](https://effect.plants.sh/testing/) and [Scheduling](https://effect.plants.sh/scheduling/) for how this plays out in
practice.
**Tip:** The same reasoning extends to randomness: read from the `Random` service rather
than `Math.random()` so tests can pin a seed.

## Avoid tacit (point-free) style

Write the function explicitly rather than passing it bare to a combinator:

```ts
declare const parse: (s: string) => number
declare const effect: Effect.Effect<string>

// ✅ Explicit — robust to overloads and clearer in stack traces
effect.pipe(Effect.map((s) => parse(s)))

// ⚠️ Tacit — can erase generics on overloaded functions and worsen inference
effect.pipe(Effect.map(parse))
```

Tacit usage looks tidy, but when the passed function has overloads or optional
parameters it can silently drop generics and produce surprising types. Spelling
out the argument is a cheap habit that prevents a class of subtle bugs.