The Effect Type
Effect<A, E, R> is the contract of a computation: its success value, expected errors, and required services. Effeck infers this contract for every function, including small functions that only calculate a value.
Success, failure, and services
error MissingRate {
currency: string
}
service Rates {
lookup(currency: string): number!MissingRate
}
function convert(amount: number, currency: string) {
return amount * Rates.lookup(currency)
}
// number!MissingRate!RatesA is number, E is MissingRate, and R is Rates. A service method states its complete contract because it has no body to infer from. Using that method also adds the service itself to the caller's requirements.
| Channel | Meaning of special types |
|---|---|
A | void returns no useful value; never never succeeds. |
E | never has no expected failure; defects and interruption remain possible. |
R | never needs no additional services or control operations. |
Errors and services from several calls accumulate as unions. Catching a failure removes that handled failure from E; providing a service removes that requirement from R. Write a full annotation when you want the compiler to check a promise, such as number!!Rates.
Write a contract
function rateOf(currency: string): number!MissingRate!Rates {
return Rates.lookup(currency)
}
function safeRate(currency: string): number!!Rates {
return Effect.orDie(() => Rates.lookup(currency))
}
function anyRate(currency: string): number!MissingRate {
return Rates.lookup(currency)
}
// Dependencies inferred: number!MissingRate!Rates.effeck code writes Effect<A, E, R> as Result!Error!Dependencies. A slot without a type is never: number!MissingRate! cannot need a service, and number!! neither fails nor needs one. On a function with a body, dependencies left out entirely, as in anyRate, are inferred from the body; on a declaration without a body, such as a service member, they are never. A union belongs to its slot: string!MissingRate | Offline!Rates fails with either error. Put a function type, a conditional type, or, after a !, an object type in parentheses; the { of an object type would start the function's body.
Hovers show contracts this way, and declaration files keep Effect.Effect<A, E, R> for TypeScript callers. Effect<A, E, R> is accepted anywhere a type is.
Check selected channels
function rate(currency: string): Effect<number, infer E, Rates> {
return Rates.lookup(currency)
}
// Checks number and Rates; infers MissingRate.
function rateOrDie(currency: string): Effect<number, never, infer R> {
return Effect.orDie(Rates.lookup(currency))
}
// Checks number and never; infers Rates.
function label(currency: string): Effect<infer A, MissingRate, Rates> {
return Rates.lookup(currency).toFixed(2)
}
// Infers string; checks MissingRate and Rates.
function checkedConversion(amount: number, currency: string): Effect<number, infer E, infer R> {
return convert(amount, currency)
}
// Checks number; infers MissingRate and Rates.In a return annotation on a function with a body, infer followed by any name infers that channel from the body. Other slots are promises the compiler checks. The name is documentation only and is not in scope in the body or other slots. infer _ is allowed, including Effect<infer _, infer _, infer _> to infer all three channels.
Write all three slots. Effect<number> in this position reports EK2003: "Did you mean never or inferred?" The fixes it suggests are number!!, to check for no failures or requirements, and removing the annotation to infer them. A plain return annotation such as : number is EK2003 on a function whose body runs an Effect; a function that compiles to plain JavaScript keeps it.
This applies only to function return annotations with bodies in .effeck. In parameter types, aliases, abstract members, and .ts files, Effect<number> keeps TypeScript's defaults: Effect<number, never, never>. Those positions do not support this channel inference. A callback parameter without an Effect contract, such as (n: number) => string, still accepts any contract.
Description and execution
const conversion = () => convert(30, "EUR")
function displayedPrice() {
const amount = conversion()
return amount.toFixed(2)
}Creating conversion does no work. Calling it runs the computation within the current program and gives you its success value. A call does not start a new fiber; runners start execution at the application edge.
An imported Effect value is also callable. For example, Clock.currentTimeMillis from effect is a description, and Clock.currentTimeMillis() reads the clock inside a function. The same call rule runs Effect-returning library functions.
The result type must be Effect itself for a call to run it automatically. Config, Exit, Context keys, and yieldable error instances carry additional structure and stay data when returned. Call such a value to run it; new only constructs it.
Extract inferred channel types
// convert.effeck
export function double(amount: number) {
return amount * 2
}
// number!!// TypeScript (.ts)
// inspect.ts: imports the emitted Effeck module
import { Effect } from "effect"
import { double } from "./convert.js"
type Contract = ReturnType<typeof double>
type Value = Effect.Success<Contract> // number
type Failure = Effect.Error<Contract> // never
type Services = Effect.Services<Contract> // neverTypeScript consumers see exported Effeck functions as functions returning ordinary Effects. Effect.Success, Effect.Error, and Effect.Services extract the channels from those inferred return types. These utilities inspect types without executing the program.
Coming from Effect
Effect<A, E, R> is available globally in .effeck files. A local call gives A; preserve a program with a function value when you need to compose or store it.