effect-pattern-matching
/effect-pattern-matching
Dopasowuje tagowane unie schematów, wartości Data.TaggedEnum i wyniki Effect przez match, guardy i $is.
- Autor
- mpsuesser ↗
- Repozytorium
- opencode-effect-enforcer ↗
- Licencja
- MIT
- Ostatnia zmiana repozytorium
- 26.10.2026
Ocena i bezpieczeństwo
- Pliki w paczce
- 1
- Skrypty
- 0
- Komentarze HTML
- 13
- Adresy URL w SKILL.md
- 0
Brak sygnałów. Repozytorium zmienione 26.10.2026, bez odwołań do wycofanych modeli i API.
MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.
- SKILL.md zawiera komentarze HTML (13). Nie widać ich w podglądzie Markdown, a model je czyta.
Co robi i kiedy
Dopasowuje tagowane unie schematów, wartości Data.TaggedEnum i wyniki Effect przez match, guardy i $is.
Oryginalny opis (EN)
Match schema tagged unions with match/matchOrElse and guards, trusted Data.TaggedEnum values with $match/$is, and Effect outcomes with Effect.match. Use for discriminated unions, ADTs, or conditional logic based on tagged types.
Jak działa
- Effect Source Reference
- Core Philosophy
- Schema-First Matching
- Pattern 1: Data.TaggedEnum for Trusted ADTs
- Pattern 2: Avoid Effect.result + \tag Checks
- Pattern 3: Use $match for Exhaustive Pattern Matching
- Pattern 4: Use $is for Single-Case Type Guards
- Pattern 5: Use Option.match Instead of \tag Checks
- Pattern 6: Use Match.typeTags for Schema Unions
- Pattern 7: Match.fn for Selector-Based Functions
Podgląd SKILL.md
---
name: effect-pattern-matching
description: Match schema tagged unions with match/matchOrElse and guards, trusted Data.TaggedEnum values with $match/$is, and Effect outcomes with Effect.match. Use for discriminated unions, ADTs, or conditional logic based on tagged types.
---
# Effect Pattern Matching Skill
Use this skill when working with discriminated unions, ADTs, conditional logic, or any type that uses `_tag` discrimination. Pattern matching provides exhaustive, type-safe alternatives to imperative conditionals.
## Effect Source Reference
Baseline: **Effect 4.0.6**. The source checkout is at
`~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
Read exact-tag files with `git show effect@4.0.6:<path>`; main may be newer.
Reference this for:
- Match source: `packages/effect/src/Match.ts`
- Data source: `packages/effect/src/Data.ts`
- Full Schema API: `packages/effect/SCHEMA.md`
- Effect source: `packages/effect/src/`
## Core Philosophy
**Pattern matching over imperative conditionals**:
- Exhaustive when finalized with `Match.exhaustive`, schema `.match`, or `$match`
- Type-safe refinement in each branch
- Declarative, not imperative
- Pipeline-friendly composition
## Schema-First Matching
For domain/wire models, prefer class variants combined with
`Schema.Union([...]).pipe(Schema.toTaggedUnion('kind'))`. Use `.match` for
exhaustiveness, `.guards` for narrowing decoded values, and `.matchOrElse` for
intentional partial matching. Direct `Schema.TaggedUnion` creates canonical
`_tag` object variants. Both expose data-first and data-last `matchOrElse` forms;
the `toTaggedUnion` fallback excludes handled variants, while direct
`Schema.TaggedUnion.matchOrElse` types its fallback as the full union.
Both expose `.tag` with the chosen discriminator key (`'_tag'` for direct
`TaggedUnion`). Duplicate discriminants are runtime construction errors, not a
compile-time uniqueness guarantee in 4.0.6.
<!-- typecheck -->
```typescript
import * as Schema from 'effect/Schema';
const State = Schema.TaggedUnion({
Loading: {},
Ready: { data: Schema.Array(Schema.String) },
Failed: { message: Schema.String }
});
type State = typeof State.Type;
const getData = State.matchOrElse({ Ready: (state) => state.data }, () => []);
const label = State.match({
Loading: () => 'Loading',
Ready: ({ data }) => `${data.length} items`,
Failed: ({ message }) => message
});
```
TypeScript correctly narrows literal discriminator checks. Prefer these helpers
for exhaustive, reusable branching, not because manual checks cannot narrow.
For the full class-based lifecycle example see `effect-domain-modeling`.
## Pattern 1: Data.TaggedEnum for Trusted ADTs
Use `Data.TaggedEnum` for trusted, non-schema ADTs. It supplies constructors and
tag checks but does not parse unknown input; avoid a parallel Data model for an
existing schema union.
### The Problem: Manual Tagged Unions
```typescript
// ❌ WRONG - Manual tagged union
type WalletState =
| { readonly _tag: 'Disconnected' }
| { readonly _tag: 'Connecting' }
| { readonly _tag: 'Connected'; readonly address: string }
| { readonly _tag: 'Error'; readonly message: string };
// Manual constructors - verbose and error-prone
const disconnected = (): WalletState => ({ _tag: 'Disconnected' });
const connecting = (): WalletState => ({ _tag: 'Connecting' });
const connected = (address: string): WalletState => ({
_tag: 'Connected',
address
});
const error = (message: string): WalletState => ({ _tag: 'Error', message });
// Constructors alone do not supply reusable matching/guard helpers.
// TypeScript can still narrow tags and check an explicit exhaustive matcher.
```
### The Solution: Data.TaggedEnum
<!-- typecheck -->
```typescript
// ✅ CORRECT - TaggedEnum with constructors + $match + $is
import { Data } from 'effect';
type WalletState = Data.TaggedEnum<{
Disconnected: {};
Connecting: {};
Connected: { readonly address: string };
Error: { readonly message: string };
}>;
const WalletState = Data.taggedEnum<WalletState>();
/**
* WalletState now provides:
* - WalletState.Disconnected() - Constructor
* - WalletState.Connecting() - Constructor
* - WalletState.Connected({ address }) - Constructor
* - WalletState.Error({ message }) - Constructor
* - WalletState.$match(state, { ... }) - Pattern matching
* - WalletState.$is("Connected")(state) - Type guard (`_tag` check only)
*/
// Usage
const state = WalletState.Connected({ address: '0x123' });
// Pattern match
const display = WalletState.$match(state, {
Disconnected: () => 'Please connect wallet',
Connecting: () => 'Connecting...',
Connected: ({ address }) => `Connected: ${address}`,
Error: ({ message }) => `Error: ${message}`
});
// Type guard
if (WalletState.$is('Connected')(state)) {
console.log(state.address); // Type-safe access
}
```
> **Caveat:** `Data.$is(tag)` / `TaggedEnum.$is(tag)` only checks the `_tag` field, not the full structure. Use it for values produced by your constructors; validate untrusted input with `Schema` before relying on `$is`.
### Benefits of Data.TaggedEnum
1. **Automatic constructors** - No manual factory functions
2. **Automatic $match** - Exhaustive pattern matching built-in
3. **Automatic $is** - Type-safe guards for each variant
4. **Type inference** - Compiler knows all variants
5. **Compile-time exhaustiveness** - Forget a case? Compiler error
### When to Use Data.TaggedEnum
- **State machines**: Connection states, loading states, workflow states
- **Domain events**: UserLoggedIn, UserLoggedOut, SessionExpired
- **Command types**: CreateUser, UpdateUser, DeleteUser
- **Result types**: Success, Failure, Pending
- **Any discriminated union** with multiple variants
## Pattern 2: Avoid Effect.result + \_tag Checks
Use `Effect.match` instead of `Effect.result` with manual tag checks.
### The Problem: Effect.result with Manual Checks
```typescript
// ❌ WRONG - Effect.result with manual _tag checks
import { Effect, Result, Schema } from 'effect';
declare const User: { name: string; id: string };
type User = typeof User;
class NotFound extends Schema.TaggedError<NotFound>()('NotFound', {
id: Schema.String
}) {}
const getUser = (id: string): Effect.Effect<User, NotFound> =>
Effect.fail(new NotFound({ id }));
const program = Effect.gen(function* () {
const result = yield* Effect.result(getUser('123'));
// This two-case branch does handle both Result variants.
if (result._tag === 'Failure') {
console.error(`User not found: ${result.failure.id}`);
return null;
}
return result.success;
});
```
**Problems:**
- This branch is valid; a generic fallback could hide newly introduced variants
- Verbose and imperative
- Breaks pipeline style
- Manual unwrapping of Result
### The Solution: Effect.match
```typescript
// ✅ CORRECT - Effect.match for declarative error handling
import { Effect, Schema } from 'effect';
declare const User: { name: string; id: string };
type User = typeof User;
class NotFound extends Schema.TaggedError<NotFound>()('NotFound', {
id: Schema.String
}) {}
const getUser = (id: string): Effect.Effect<User, NotFound> =>
Effect.fail(new NotFound({ id }));
const program = getUser('123').pipe(
Effect.match({
onFailure: (error) => {
console.error(`User not found: ${error.id}`);
return null;
},
onSuccess: (user) => user
})
);
```
**Benefits:**
- Exhaustive (must handle both cases)
- Declarative and pipeline-friendly
- No manual Result unwrapping
- Type-safe refinement in each branch
### Effect.match Variants
```typescript
import { Effect, Cause } from 'effect';
declare const effect: Effect.Effect<unknown, unknown, unknown>;
declare function handleError(error: unknown): unknown;
declare function handleSuccess(value: unknown): unknown;
declare function handleCause(cause: Cause.Cause<unknown>): unknown;
// Basic match - transform both success and failure
Effect.match(effect, {
onFailure: (error) => handleError(error),
onSuccess: (value) => handleSuccess(value)
});
// matchEffect - return Effects from handlers
Effect.matchEffect(effect, {
onFailure: (error) => Effect.logError(error).pipe(Effect.as(null)),
onSuccess: (value) => Effect.succeed(value)
});
// matchCause - match on full Cause (errors + defects + interrupts)
Effect.matchCause(effect, {
onFailure: (cause) => handleCause(cause),
onSuccess: (value) => value
});
// matchCauseEffect - Cause matching with Effect handlers
Effect.matchCauseEffect(effect, {
onFailure: (cause) => Effect.logError(cause).pipe(Effect.as(null)),
onSuccess: (value) => Effect.succeed(value)
});
```
## Pattern 3: Use $match for Exhaustive Pattern Matching
Use `TaggedEnum.$match` for exhaustive, type-safe pattern matching.
### The Problem: if/else Chains
```typescript
// ❌ WRONG - if/else chains, not exhaustive
import { Data } from 'effect';
type Status = Data.TaggedEnum<{
Active: {};
Expired: {};
Revoked: {};
}>;
const Status = Data.taggedEnum<Status>();
const getColor = (status: Status): string => {
if (status._tag === 'Active') {
return 'green';
} else if (status._tag === 'Expired') {
return 'yellow';
}
// Forgot "Revoked" - no compiler error!
return 'gray';
};
```
**Problems:**
- Not exhaustive (easy to forget cases)
- Compiler doesn't enforce completeness
- Imperative style
- Hard to refactor when adding variants
### The Solution: $match
<!-- typecheck -->
```typescript
// ✅ CORRECT - $match with exhaustive checking
import { Data } from 'effect';
type Status = Data.TaggedEnum<{
Active: {};
Expired: {};
Revoked: {};
}>;
const Status = Data.taggedEnum<Status>();
const getColor = (status: Status): string =>
Status.$match(status, {
Active: () => 'green',
Expired: () => 'yellow',
Revoked: () => 'red'
// Compiler error if any case is missing!
});
```
**Benefits:**
- **Exhaustive** - Compiler enforces all cases
- **Type-safe** - Each handler gets refined type
- **Declarative** - Clear mapping from variant to result
- **Refactor-safe** - Add variant? Compiler finds all matches to update
### $match with Data Access
```typescript
type AsyncState = Data.TaggedEnum<{
Idle: {};
Loading: {};
Success: { readonly data: string };
Failure: { readonly error: string };
}>;
const AsyncState = Data.taggedEnum<AsyncState>();
const display = (state: AsyncState): string =>
AsyncState.$match(state, {
Idle: () => 'Not started',
Loading: () => 'Loading...',
Success: ({ data }) => `Loaded: ${data}`,
Failure: ({ error }) => `Error: ${error}`
});
```
### Nested Pattern Matching
```typescript
type Request = Data.TaggedEnum<{
Pending: {};
Approved: { readonly by: string };
Rejected: { readonly reason: string };
}>;
const Request = Data.taggedEnum<Request>();
type Workflow = Data.TaggedEnum<{
Draft: { readonly request: Request };
Submitted: { readonly request: Request };
Completed: {};
}>;
const Workflow = Data.taggedEnum<Workflow>();
const getStatus = (workflow: Workflow): string =>
Workflow.$match(workflow, {
Draft: ({ request }) =>
Request.$match(request, {
Pending: () => 'Draft - Pending',
Approved: ({ by }) => `Draft - Approved by ${by}`,
Rejected: ({ reason }) => `Draft - Rejected: ${reason}`
}),
Submitted: ({ request }) =>
Request.$match(request, {
Pending: () => 'Submitted - Awaiting approval',
Approved: ({ by }) => `Submitted - Approved by ${by}`,
Rejected: ({ reason }) => `Submitted - Rejected: ${reason}`
}),
Completed: () => 'Completed'
});
```
## Pattern 4: Use $is for Single-Case Type Guards
Use `TaggedEnum.$is` instead of manual `_tag` checks. It only checks `_tag`, so validate untrusted input with `Schema` before using it as a structural guarantee.
### The Problem: Manual \_tag Checks
<!-- typecheck -->
```typescript
// Valid narrowing; prefer a reusable guard when the check is repeated.
import { Data } from 'effect';
type Status = Data.TaggedEnum<{
Active: {};
Expired: {};
}>;
const Status = Data.taggedEnum<Status>();
// Verbose and repetitive
const status = Status.Active();
if (status._tag === 'Active') {
console.log('Active!');
}
// TypeScript can infer a narrowing predicate here too.
const items: Status[] = [Status.Active(), Status.Expired()];
const activeItems = items.filter((item) => item._tag === 'Active');
```
### The Solution: $is Type Guards
<!-- typecheck -->
```typescript
// ✅ CORRECT - $is for type-safe guards
import { Data, Array, pipe } from 'effect';
type Status = Data.TaggedEnum<{
Active: {};
Expired: {};
}>;
const Status = Data.taggedEnum<Status>();
const status = Status.Active();
// Clean, declarative guard
if (Status.$is('Active')(status)) {
console.log('Active!');
}
// Perfect for Array methods
const items: Status[] = [Status.Active(), Status.Expired()];
const activeItems = items.filter(Status.$is('Active'));
// Pipeline-friendly
const hasActive = pipe(items, Array.some(Status.$is('Active')));
// Multiple guards
const activeOrExpired = items.filter(
(item) => Status.$is('Active')(item) || Status.$is('Expired')(item)
);
```
### $is in Effect Pipelines
<!-- typecheck -->
```typescript
import { Data, pipe } from 'effect';
type LoadState = Data.TaggedEnum<{
Loading: {};
Ready: { readonly data: string[] };
Error: { readonly message: string };
}>;
const LoadState = Data.taggedEnum<LoadState>();
const getData = LoadState.$match({
Loading: () => [],
Ready: ({ data }) => data,
Error: () => []
});
// A guard returns boolean; piping through $is would lose the original value.
```
## Pattern 5: Use Option.match Instead of \_tag Checks
Use `Option.match` instead of manual `._tag` checks on Options.
### The Problem: Manual Option Tag Checks
```typescript
// ❌ WRONG - Manual Option._tag checks
import { Option } from 'effect';
type User = { name: string; id: string };
const maybeUser: Option.Option<User> = Option.some({
name: 'Alice',
id: '123'
});
// Imperative and verbose
if (maybeUser._tag === 'Some') {
console.log(maybeUser.value.name);
} else {
console.log('No user');
}
```
### The Solution: Option.match
<!-- typecheck -->
```typescript
// ✅ CORRECT - Option.match
import { Option, pipe } from 'effect';
type User = { name: string; id: string };
const maybeUser: Option.Option<User> = Option.some({
name: 'Alice',
id: '123'
});
const display = Option.match(maybeUser, {
onNone: () => 'No user',
onSome: (user) => user.name
});
// In pipelines
const name = pipe(
maybeUser,
Option.match({
onNone: () => 'Guest',
onSome: (user) => user.name
})
);
```
### Option Pattern Matching Variants
```typescript
import { Option, pipe } from 'effect';
declare const option: Option.Option<string>;
declare const defaultValue: string;
declare function transform(value: string): string;
declare function predicate(value: string): boolean;
// Basic match
Option.match(option, {
onNone: () => defaultValue,
onSome: (value) => transform(value)
});
// getOrElse - simpler for just default value
Option.getOrElse(option, () => defaultValue);
// map + getOrElse pattern
pipe(
option,
Option.map(transform),
Option.getOrElse(() => defaultValue)
);
// filter + match
pipe(
option,
Option.filter(predicate),
Option.match({
onNone: () => 'Filtered out or was None',
onSome: (value) => `Matched: ${value}`
})
);
```
## Pattern 6: Use Match.typeTags for Schema Unions
For Schema-based unions, use `Match.typeTags` for pattern matching.
### Schema Union Pattern Matching
<!-- typecheck -->
```typescript
import { Schema, Match } from 'effect';
// Schema-based tagged structs
const Admin = Schema.TaggedStruct('Admin', {
id: Schema.String,
permissions: Schema.Array(Schema.String)
});
const Customer = Schema.TaggedStruct('Customer', {
id: Schema.String,
tier: Schema.Literals(['free', 'premium'])
});
const User = Schema.Union([Admin, Customer]);
type User = Schema.Schema.Type<typeof User>;
// Match.typeTags for Schema unions
const getPermissions = Match.typeTags<User>()({
Admin: ({ permissions }) => permissions,
Customer: ({ tier }) => (tier === 'premium' ? ['read'] : [])
});
const user: User = {
_tag: 'Admin' as const,
id: '1',
permissions: ['read', 'write']
};
const perms = getPermissions(user); // ["read", "write"]
```
### Match.typeTags Pattern
```typescript
import { Match, Data } from 'effect';
type UnionType = Data.TaggedEnum<{
VariantA: { field: string };
VariantB: { other: number };
}>;
declare const value: UnionType;
declare function handleA(data: { field: string }): string;
declare function handleB(data: { other: number }): string;
// Create matcher function
const match = Match.typeTags<UnionType>();
// Use with handlers object
const result = match({
VariantA: (data) => handleA(data),
VariantB: (data) => handleB(data)
})(value);
// Or create matcher and apply later
const matcher = match({
VariantA: (data) => handleA(data),
VariantB: (data) => handleB(data)
});
const result2 = matcher(value);
```
## Pattern 7: Match.fn for Selector-Based Functions
Use `Match.fn(selector)` when a reusable function takes several arguments or should match a projection of its input. The selector chooses the value matched by `when` / `tag` / other cases, while the compiled matcher preserves the selector's original argument list.
Case handlers receive the narrowed selected value first, followed by all original selector arguments:
<!-- typecheck -->
```typescript
import { Match } from 'effect';
type Todo = {
readonly status: 'Active' | 'Completed';
readonly title: string;
};
const formatTodo = Match.fn((prefix: string, todo: Todo) => todo.status).pipe(
Match.when(
'Active',
(_status, prefix, todo) => `${prefix}: active ${todo.title}`
),
Match.when(
'Completed',
(_status, prefix, todo) => `${prefix}: completed ${todo.title}`
),
Match.exhaustive
);
formatTodo('Todo', { status: 'Active', title: 'Write tests' });
// "Todo: active Write tests"
```
`Match.exhaustive`, `Match.orElse`, `Match.option`, and `Match.result` all compile a `Match.fn` matcher to a function with the selector's original parameters. Prefer `Match.type<I>()` when the matched value itself is the function's only input; use `Match.fn` when matching one argument, a derived property, or another projection while retaining surrounding arguments.
### Object keys and tuple-prefix patterns in 4.0.6
`when`, `not`, `whenOr`, and `whenAnd` reject object-pattern keys absent from
every remaining object variant. Numeric keys normalize to string keys for this
check. It is a top-level typo guard, not recursive exact-object validation;
functions, refinements, and array patterns are excluded. Runtime matching is
still partial structural matching, not boundary decoding.
Tuple patterns match a prefix, not an exact array length. With variadic/array
inputs, required prefix positions narrow while the remaining input is retained.
Do not use `[]` as an empty-array test: it is an empty prefix.
<!-- typecheck -->
```typescript
import { Match } from 'effect';
const prefix = Match.type<ReadonlyArray<string | number>>().pipe(
Match.when([Match.string, Match.number], (values) => {
const first: string = values[0];
const second: number = values[1];
return `${first}: ${second}`;
}),
Match.orElse(() => 'No matching prefix')
);
prefix(['item', 2, 'tail']); // 'item: 2'
```
## Pattern 8: Loadable.match for Async State
Use an application's `Loadable.match` for async state pattern matching. The
`@/typeclass/Loadable` below is an illustrative local module, not an Effect export.
### Loadable Pattern
```tsx
import { Loadable } from '@/typeclass/Loadable';
type User = { name: string; id: string };
declare const Spinner: () => JSX.Element;
declare const UserProfile: (props: { user: User }) => JSX.Element;
declare const ErrorDisplay: (props: { error: Error }) => JSX.Element;
type UserData = Loadable.Loadable<User>;
const display = (data: UserData): JSX.Element =>
Loadable.match(data, {
onPending: () => <Spinner />,
onReady: (user) => <UserProfile user={user} />
});
// With error state
type UserDataWithError = Loadable.LoadableWithError<User, Error>;
const displayWithError = (data: UserDataWithError): JSX.Element =>
Loadable.matchWithError(data, {
onPending: () => <Spinner />,
onReady: (user) => <UserProfile user={user} />,
onError: (error) => <ErrorDisplay error={error} />
});
```
## Testability: Effect Services
When pattern matching involves non-deterministic operations, use Effect services.
### The Problem: Untestable Direct Calls
```typescript
// ❌ WRONG - untestable
import { Data } from 'effect';
type State = Data.TaggedEnum<{
Active: {};
Expired: {};
}>;
const State = Data.taggedEnum<State>();
const processState = (state: State): string =>
State.$match(state, {
Active: () => `Active at ${Date.now()}`,
Expired: () => `Expired at ${Date.now()}`
});
```
### The Solution: Effect Services
<!-- typecheck -->
```typescript
// ✅ CORRECT - testable with Clock service
import { Clock, Effect, Data } from 'effect';
import { TestClock } from 'effect/testing';
type State = Data.TaggedEnum<{
Active: {};
Expired: {};
}>;
const State = Data.taggedEnum<State>();
const processState = (state: State): Effect.Effect<string> =>
Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis;
return State.$match(state, {
Active: () => `Active at ${now}`,
Expired: () => `Expired at ${now}`
});
});
// In tests, use TestClock for deterministic time
const testProgram = processState(State.Active()).pipe(
Effect.provide(TestClock.layer())
);
```
### Random Values in Pattern Matching
```typescript
// ❌ WRONG - untestable
import { Data } from 'effect';
type User = Data.TaggedEnum<{
Admin: {};
Customer: {};
}>;
const User = Data.taggedEnum<User>();
const assignColor = (user: User): string =>
User.$match(user, {
Admin: () => 'red',
Customer: () => (Math.random() > 0.5 ? 'blue' : 'green')
});
// ✅ CORRECT - testable with Random service
import { Random, Effect } from 'effect';
const assignColorTestable = (user: User): Effect.Effect<string> =>
User.$match(user, {
Admin: () => Effect.succeed('red'),
Customer: () =>
Effect.gen(function* () {
const rand = yield* Random.next;
return rand > 0.5 ? 'blue' : 'green';
})
});
```
## Complete Example: Wallet Connection State Machine
<!-- typecheck -->
```typescript
import { Data, Effect, Clock } from 'effect';
// Define state machine with TaggedEnum
type WalletState = Data.TaggedEnum<{
Disconnected: {};
Connecting: { readonly startedAt: number };
Connected: {
readonly address: string;
readonly connectedAt: number;
};
Error: {
readonly message: string;
readonly occurredAt: number;
};
}>;
const WalletState = Data.taggedEnum<WalletState>();
// State transitions
const connect = (): Effect.Effect<WalletState> =>
Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis;
return WalletState.Connecting({ startedAt: now });
});
const completeConnection = (address: string): Effect.Effect<WalletState> =>
Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis;
return WalletState.Connected({
address,
connectedAt: now
});
});
const fail = (message: string): Effect.Effect<WalletState> =>
Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis;
return WalletState.Error({
message,
occurredAt: now
});
});
// Pattern match for display
const displayState = (state: WalletState): string =>
WalletState.$match(state, {
Disconnected: () => 'Please connect your wallet',
Connecting: ({ startedAt }) =>
`Connecting... (started at ${startedAt})`,
Connected: ({ address, connectedAt }) =>
`Connected: ${address} (at ${connectedAt})`,
Error: ({ message, occurredAt }) =>
`Error: ${message} (at ${occurredAt})`
});
// Type-safe state queries using $is
const isConnected = WalletState.$is('Connected');
const canDisconnect = (state: WalletState): boolean =>
isConnected(state) || WalletState.$is('Error')(state);
// Filter connected states
const getConnectedStates = (
states: WalletState[]
): Array<Extract<WalletState, { _tag: 'Connected' }>> =>
states.filter(isConnected);
```
## Quality Checklist
Before completing pattern matching implementation:
- [ ] Use `Data.TaggedEnum` for ADTs (not manual tagged unions)
- [ ] Use `TaggedEnum.$match` for exhaustive matching
- [ ] Use `TaggedEnum.$is` for type guards (not `._tag === `)
- [ ] Use `Effect.match` instead of `Effect.result` + if checks
- [ ] Use `Option.match` instead of `Option._tag` checks
- [ ] Use `Match.typeTags` for Schema union matching
- [ ] Use `Match.fn` when matching a selector while preserving multiple function arguments
- [ ] All pattern matches are exhaustive (compiler-checked)
- [ ] Use `Clock` service instead of `Date.now()` in matches
- [ ] Use `Random` service instead of `Math.random()` in matches
- [ ] Pattern matching is declarative (no imperative conditionals)
- [ ] Pipeline-friendly composition
- [ ] Type-safe refinement in each branch
## Common Patterns Summary
### ADT Definition
<!-- typecheck -->
```typescript
import { Data } from 'effect';
type State = Data.TaggedEnum<{
VariantA: { field: string };
VariantB: { other: number };
}>;
const State = Data.taggedEnum<State>();
```
### Exhaustive Matching
```typescript
import { Data } from 'effect';
type State = Data.TaggedEnum<{
VariantA: { field: string };
VariantB: { other: number };
}>;
const State = Data.taggedEnum<State>();
declare const state: State;
declare function handleA(field: string): void;
declare function handleB(other: number): void;
State.$match(state, {
VariantA: ({ field }) => handleA(field),
VariantB: ({ other }) => handleB(other)
});
```
### Type Guards
```typescript
import { Data } from 'effect';
type State = Data.TaggedEnum<{
VariantA: { field: string };
VariantB: { other: number };
}>;
const State = Data.taggedEnum<State>();
declare const state: State;
declare const items: State[];
if (State.$is('VariantA')(state)) {
// state is refined to VariantA
}
// In filters
items.filter(State.$is('VariantA'));
```
### Effect Matching
```typescript
import { Effect } from 'effect';
declare const effect: Effect.Effect<unknown, unknown, unknown>;
declare function handleError(error: unknown): unknown;
declare function handleSuccess(value: unknown): unknown;
effect.pipe(
Effect.match({
onFailure: (error) => handleError(error),
onSuccess: (value) => handleSuccess(value)
})
);
```
### Option Matching
```typescript
import { Option } from 'effect';
declare const option: Option.Option<string>;
declare const defaultValue: string;
declare function transform(value: string): string;
Option.match(option, {
onNone: () => defaultValue,
onSome: (value) => transform(value)
});
```
Your pattern matching implementations should be exhaustive, type-safe, declarative, and testable.
Wersje
W bazie nie ma innych skilli o nazwie effect-pattern-matching.
Migawka bazy z 28.10.2026 · wersja paczki 83bbe9d55939 (skrót SHA-256 całej paczki)
Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.
Podobne skille
- 100effect-domain-modeling
Buduje modele domenowe Effect oparte na schemacie z wariantami Schema.Class, uniami tagowanymi, wartościami markowanymi i legalnymi przejściami stanów. - 95effect-v4-schema
Projektuje i sprawdza schematy Effect v4: wybór klasy lub struktury, pola, unie tagowane, transformacje i kodeki. - 95effect-schema
Opisuje wzorce @effect/schema: definiowanie schematów, walidację, parsowanie, kodowanie i transformacje dla typobezpiecznej walidacji danych w Effect. - 100effect-v3-to-v4
Prowadzi migrację kodu z Effect v3 do Effect v4, w tym aktualizację pakietów effect i @effect/* w obrębie tej granicy wersji. - 100fsharp
Wspiera pisanie kodu w F# na .NET: unie rozłączne, dopasowanie wzorców, computation expressions, modelowanie domeny, potoki danych i współpracę z C#. - 100schema-first-development
Prowadzi przy schematach effect/Schema i @beep/schema: modelowanie typów, dekodowanie danych i poprawki lintów.