Skip to content

Instantly share code, notes, and snippets.

@sellooh
Created June 5, 2025 20:07
Show Gist options
  • Save sellooh/90d0dffd58f42ae0a0afac064185e80f to your computer and use it in GitHub Desktop.
Save sellooh/90d0dffd58f42ae0a0afac064185e80f to your computer and use it in GitHub Desktop.
# Effect-TS Overview
Effect-TS is a TypeScript library for building robust, type-safe, and scalable applications by leveraging functional programming concepts.
## Core Concept: `Effect<A, E, R>`
The central type in Effect-TS, representing a computation:
- `A`: The **Success Type** (value produced on success).
- `E`: The **Error Type** (typed error on failure, unlike `any` in Promises).
- `R`: The **Requirement/Environment Type** (dependencies needed, e.g., database, logger).
## Key Benefits:
1. **Enhanced Type Safety:** Errors (`E`) and Requirements (`R`) in type signatures catch more issues at compile-time.
2. **Robust Error Handling:** Typed errors allow precise handling (e.g., `Effect.catchTags`, `Effect.catchAll`).
3. **Structured Dependency Management:** Built-in, type-safe DI via `Context`, `Tag`, `Service`, `Layer`. Dependencies are explicit.
4. **Composable Asynchronous Operations:** Build complex async workflows from smaller, manageable effects (e.g., using `Effect.gen` or `Effect.pipe`).
* **Example with `Effect.gen` (using `yield*`):**
This approach allows writing asynchronous-looking code that maintains full type information for errors and requirements. `yield*` is used to delegate to another Effect.
```typescript
import { Effect, Console } from "effect";
const getUser = (id: number): Effect.Effect<{ id: number; name: string }, string> =>
id === 1
? Effect.succeed({ id: 1, name: "Alice" })
: Effect.fail(\`User with id ${id} not found\`);
const getGreeting = (name: string): Effect.Effect<string, never, Console.Console> =>
Console.log(\`Fetching greeting for ${name}...\`).pipe(
Effect.flatMap(() => Effect.succeed(\`Hello, ${name}!\`))
);
const program = Effect.gen(function* () {
const user = yield* getUser(1); // Success: { id: 1, name: "Alice" }
// const user = yield* getUser(2); // Failure: "User with id 2 not found"
const greeting = yield* getGreeting(user.name);
yield* Console.log(greeting);
});
// To run this:
// import { NodeRuntime } from "@effect/platform-node";
// NodeRuntime.runMain(program.pipe(Effect.provide(Console.Live)));
```
* **Comparison with `async/await`:**
`async/await` in standard TypeScript is built on Promises. Promises inherently handle errors as untyped (`any`), and dependencies are managed implicitly or manually.
```typescript
// Equivalent logic with async/await (simplified error/dependency handling)
async function getUserAsync(id: number): Promise<{ id: number; name: string }> {
if (id === 1) {
return { id: 1, name: "Alice" };
} else {
throw new Error(\`User with id ${id} not found\`); // Untyped error
}
}
async function getGreetingAsync(name: string): Promise<string> {
console.log(\`Fetching greeting for ${name}...\`); // Implicit dependency on console
return \`Hello, ${name}!\`;
}
async function mainAsync() {
try {
const user = await getUserAsync(1);
const greeting = await getGreetingAsync(user.name);
console.log(greeting);
} catch (error) { // error is 'any'
console.error("An error occurred:", error);
}
}
// mainAsync();
```
Key differences highlighted by `Effect.gen` vs `async/await`:
* **Typed Errors:** `Effect` signatures explicitly state error types (`string` in `getUser`). `async/await` throws `any`.
* **Explicit Dependencies:** `Effect` signatures can state requirements (`Console.Console` in `getGreeting`). `async/await` relies on ambient context or manual passing.
* **Composition:** Effects compose while preserving all type information (A, E, R). Promise composition loses some of this explicitness.
* **Deferred Execution:** `program` is a *description* of work. `mainAsync` executes immediately.
5. **Programs as Values (Declarative):** `Effect` describes a computation; execution is separate (e.g., `NodeRuntime.runMain`). This gives more control and testability.
6. **Resource Safety:** Manages resources (files, connections) safely, ensuring acquisition and release (e.g., `ManagedRuntime`, `Scope`).
## How Effect Differs from Regular TypeScript:
1. **Explicit Effects vs. Implicit Side Effects:**
* **TypeScript:** Side effects are implicit and untracked by types.
* **Effect:** Side effects are explicit `Effect` values, describing operations. Execution is managed by the Effect runtime.
2. **Error Handling:**
* **TypeScript (Promises):** `try/catch` with untyped errors (`any`).
* **Effect:** Typed errors (`E`) allow specific and predictable handling.
3. **Dependency Management:**
* **TypeScript:** No built-in DI; manual passing or third-party libraries.
* **Effect:** Built-in, type-safe DI (`Context`, `Layer`). Requirements (`R`) are part of the type.
4. **Concurrency & Asynchronicity:**
* **TypeScript (Promises/async/await):** Baseline async support.
* **Effect:** Advanced, composable concurrency (Fibers, structured concurrency), resource-safe async ops, testable time (`TestClock`).
5. **Functional Programming Focus:**
* **TypeScript:** Can use FP, but not enforced.
* **Effect:** Designed around FP; Effects are typically immutable.
6. **Execution Model:**
* **TypeScript:** Functions execute when called.
* **Effect:** `Effect` values are descriptions executed by a runtime, enabling features like interruptibility and retries.
## Summary:
Effect-TS offers a paradigm shift for building TypeScript applications, emphasizing type-safety, robustness, and scalability through functional programming. It has a steeper learning curve but provides significant benefits for complex systems where reliability is crucial. Requires `"strict": true` in `tsconfig.json`.
## Searching docs
To find anything about Effect, use the context7 MCP to find precise documentation and examples of Effect code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment