Skip to content
LogoLogo

VIP-12: provide blocks

Status: accepted by Will, 2026-10-28.

Decision

// VibeLang
provide AppLive, Users = testUsers {
  try a()
  try b()
}
 
// exactly this Effect TS
yield* Effect.provide(
  Effect.gen(function* () { yield* a(); yield* b() }),
  [AppLive, Layer.succeed(Users, testUsers)]
)
  • provide providers { … } is exact sugar for Effect.provide. It is a statement that runs where it stands, like do { }, and every Effect inside it is still run with try.

  • A provider is a Layer, or Key = value, which means Layer.succeed(Key, value). Effect's own provide types everything, so VibeLang adds no typing rules of its own.

  • Ambiguity: none. provide is a contextual keyword, and its providers and block must be on one line, as with async. Trailing blocks don't exist (VIP-11), so provide(x) { … } can't be read as a call.

  • Amended 2026-10-29: these are ordinary blocks. return, break and continue inside them reach the enclosing function or loop, as in any TypeScript block. (for concurrent and for try keep their own rule, because their bodies run once per item.) Recorded under Will's standing approval of recommendations.

  • Amended 2026-10-29: when provide is a keyword. provide is a keyword only when the tokens from provide through a { on the line where the last provider ends parse as the head of a provide statement, as an arrow needs its whole head. Otherwise it is an identifier, in any code: provide(x), provide[0] = 1 and provide - 1 stay TypeScript. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 34).

  • Amended 2026-10-29: no line break at a provider comma. A line break after a comma between providers is a syntax error, as "its providers and block must be on one line" says. Breaks inside a provider are free. The formatter never breaks a provide list at a comma; a long list stays on one line or uses Layer.mergeAll(…). Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 35).

  • Under VIP-6's rule, a keyword removes the global of the same name, so Effect's function is written Effect.provide.

  • Amended 2026-10-30: observer payloads and source completions. A provide or interruption region preserves its source completion separately from the primitive Effect success value. Normal completion and outward break/continue give primitive success undefined; a source return value gives that exact value, and bare return gives undefined. Nested regions apply this at each boundary. Finalizers observe these generator payloads, never compiler control records; a normal source value still contributes to an enclosing do. Real returned Effects and record-shaped objects remain data. Completion state is fresh for each execution/item, and failed, dying or interrupted bodies/finalizers transfer no pending source completion. This preserves the original generator counterpart and Effect's own finalization rules; see Region bodies.

  • Amended 2026-10-30: bounded provider-comma recovery. At a statement start, a head otherwise parsing through its body-opening { remains recognized when only a line break immediately before or after a top-level provider-separating comma violates the grammar. This recovered head stays invalid and uses EK1202 on either side; outside Effect code the existing EK1201 rule still applies. Relax only comma-adjacent restrictions for recognition: keep the ordinary provider grammar, no break after provide and same-line final {. No broader body search or global keyword reservation is added. Breaks inside provider expressions, ordinary calls without that body and expression readings after return are unchanged. Preserve native provider evaluation, bindings, typing and runtime behavior; invalid heads have no lowering.

History

A union of providers was mistyped in round 1 (local, fixed). Nothing since.

  • Amended 2026-10-30: ordinary-block completion values. A transparent block preserves the source completion's kind, value (including empty) and target. A surrounding do can observe its normal value or a value carried through a consumed jump; generated callbacks do not discard it. The compiler's transport is its choice; transfer the completion only after the region Effect and its finalizers succeed. Failures and finalization retain Effect's rules.

Prior art

Effect's Effect.provide; Kotlin's withContext(ctx) { … }.

Will's words

"I really like this syntax"

"Ok so with that I'm pretty sure all the issues for service and provide go away in which case I accept the proposal"

Corrected 2026-10-29: the example names a declared service (Users), so it runs.