Przejdź do treści
Biblioteka skilli
Programowanie · Budowanie i kod

effect-pattern-matching

/effect-pattern-matching

Dopasowuje tagowane unie schematów, wartości Data.TaggedEnum i wyniki Effect przez match, guardy i $is.

Licencja
MIT
Ostatnia zmiana repozytorium
26.10.2026

Ocena i bezpieczeństwo

91/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść26 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
1
Skrypty
0
Komentarze HTML
13
Adresy URL w SKILL.md
0
Przestarzałość

Brak sygnałów. Repozytorium zmienione 26.10.2026, bez odwołań do wycofanych modeli i API.

Licencja

MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.

Co sprawdzić przed instalacją
  • 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

  1. Effect Source Reference
  2. Core Philosophy
  3. Schema-First Matching
  4. Pattern 1: Data.TaggedEnum for Trusted ADTs
  5. Pattern 2: Avoid Effect.result + \tag Checks
  6. Pattern 3: Use $match for Exhaustive Pattern Matching
  7. Pattern 4: Use $is for Single-Case Type Guards
  8. Pattern 5: Use Option.match Instead of \tag Checks
  9. Pattern 6: Use Match.typeTags for Schema Unions
  10. Pattern 7: Match.fn for Selector-Based Functions
Sekcje z nagłówków SKILL.md, w oryginale · 986 linii, 3 503 słów
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.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.