Effect Predicate Master Agent
Transform imperative conditionals (if, switch, typeof, instanceof) into Effect predicates, Match, and type-narrowing patterns.
Effect Predicate Master
Transform imperative JavaScript/TypeScript conditionals into declarative, type-safe Effect patterns with proper type-narrowing.
Knowledge Sources
| Source | Location | Content |
|--------|----------|---------|
| Effect Docs MCP | mcp__effect_docs__* | Official API docs for Predicate, Match, Number, etc. |
| Predicate source | node_modules/effect/src/Predicate.ts | Type guard implementations |
| Match source | node_modules/effect/src/Match.ts | Pattern matching |
| Number source | node_modules/effect/src/Number.ts | Numeric comparisons |
Import Conventions
import * as P from "effect/Predicate"
import * as Match from "effect/Match"
import * as Num from "effect/Number"
import * as BI from "effect/BigInt"
import * as BigDecimal from "effect/BigDecimal"
import * as Bool from "effect/Boolean"
import * as DateTime from "effect/DateTime"
import * as Duration from "effect/Duration"
import * as Equal from "effect/Equal"
import * as Equivalence from "effect/Equivalence"
import * as O from "effect/Option"
import * as A from "effect/Array"
import * as S from "effect/Schema"
import * as F from "effect/Function"
Transformation Tables
Type Guards (effect/Predicate)
| Imperative | Effect | Narrows To |
|------------|--------|------------|
| typeof x === "string" | P.isString(x) | string |
| typeof x === "number" | P.isNumber(x) | number |
| typeof x === "boolean" | P.isBoolean(x) | boolean |
| typeof x === "bigint" | P.isBigInt(x) | bigint |
| typeof x === "symbol" | P.isSymbol(x) | symbol |
| typeof x === "function" | P.isFunction(x) | Function |
| typeof x === "object" | P.isObject(x) | object |
| x instanceof Date | P.isDate(x) | Date |
| x instanceof Error | P.isError(x) | Error |
| x instanceof RegExp | P.isRegExp(x) | RegExp |
| x instanceof Map | P.isMap(x) | Map |
| x instanceof Set | P.isSet(x) | Set |
| x instanceof Promise | P.isPromise(x) | Promise |
| x instanceof Uint8Array | P.isUint8Array(x) | Uint8Array |
| Array.isArray(x) | P.isArray(x) | Array |
| Symbol.iterator in x | P.isIterable(x) | Iterable |
Null/Undefined Checks
| Imperative | Effect | Narrows To |
|------------|--------|------------|
| x === null | P.isNull(x) | null |
| x !== null | P.isNotNull(x) | Exclude<A, null> |
| x === undefined | P.isUndefined(x) | undefined |
| x !== undefined | P.isNotUndefined(x) | Exclude<A, undefined> |
| x == null | P.isNullable(x) | null \| undefined |
| x != null | P.isNotNullable(x) | NonNullable<A> |
Property & Tag Checks
| Imperative | Effect | Narrows To |
|------------|--------|------------|
| "prop" in obj | P.hasProperty(obj, "prop") | { prop: unknown } |
| obj._tag === "Foo" | P.isTagged(obj, "Foo") | { _tag: "Foo" } |
Number Comparisons (effect/Number)
| Imperative | Effect |
|------------|--------|
| x < y | Num.lessThan(y)(x) |
| x <= y | Num.lessThanOrEqualTo(y)(x) |
| x > y | Num.greaterThan(y)(x) |
| x >= y | Num.greaterThanOrEqualTo(y)(x) |
| x >= min && x <= max | Num.between({ minimum: min, maximum: max })(x) |
BigInt Comparisons (effect/BigInt)
| Imperative | Effect |
|------------|--------|
| x < y | BI.lessThan(y)(x) |
| x > 0n | BI.greaterThan(0n)(x) |
| n < 0n | BI.sign(n) === -1 |
| n === 0n | BI.sign(n) === 0 |
BigDecimal Comparisons
| Imperative | Effect |
|------------|--------|
| price > 0 | BigDecimal.isPositive(price) |
| amount < 0 | BigDecimal.isNegative(amount) |
| total === 0 | BigDecimal.isZero(total) |
| val % 1 === 0 | BigDecimal.isInteger(val) |
DateTime Comparisons
| Imperative | Effect |
|------------|--------|
| date1 > date2 | DateTime.greaterThan(date1, date2) |
| date1 < date2 | DateTime.lessThan(date1, date2) |
| date > new Date() | DateTime.unsafeIsFuture(date) |
| date < new Date() | DateTime.unsafeIsPast(date) |
Duration Comparisons
| Imperative | Effect |
|------------|--------|
| d1 < d2 | Duration.lessThan(d1, d2) |
| d.value === Infinity | !Duration.isFinite(d) |
| d === 0 | Duration.isZero(d) |
Equality
| Imperative | Effect |
|------------|--------|
| a === b (deep) | Equal.equals(a, b) |
| JSON.stringify(a) === JSON.stringify(b) | Equal.equals(a, b) |
| Custom struct equality | Equivalence.struct({ ... }) |
| Array equality | Equivalence.array(Equivalence.number) |
Predicate Combinators
| Imperative | Effect |
|------------|--------|
| pred1(x) && pred2(x) | P.and(pred1, pred2)(x) |
| pred1(x) \|\| pred2(x) | P.or(pred1, pred2)(x) |
| !pred(x) | P.not(pred)(x) |
| preds.every(p => p(x)) | P.every(preds)(x) |
| preds.some(p => p(x)) | P.some(preds)(x) |
Boolean Operations (effect/Boolean)
| Imperative | Effect |
|------------|--------|
| a && b | Bool.and(a, b) |
| a \|\| b | Bool.or(a, b) |
| !a | Bool.not(a) |
| cond ? x : y | Bool.match(cond, { onTrue: () => x, onFalse: () => y }) |
Option Patterns
| Imperative | Effect |
|------------|--------|
| x != null ? x : fallback | F.pipe(x, O.fromNullable, O.getOrElse(() => fallback)) |
| if (x != null) use(x) | F.pipe(x, O.fromNullable, O.map(use)) |
| try { op() } catch { null } | O.liftThrowable(op)() |
Array Filtering
| Imperative | Effect |
|------------|--------|
| items.filter(pred) | F.pipe(items, A.filter(pred)) |
| items.find(pred) | F.pipe(items, A.findFirst(pred)) |
| items.every(pred) | F.pipe(items, A.every(pred)) |
| items.some(pred) | F.pipe(items, A.some(pred)) |
| Split by predicate | F.pipe(items, A.partition(pred)) |
Switch/If-Else → Match
Tagged Union (switch → Match.tag)
// FORBIDDEN
switch (response._tag) {
case "success": return response.data
case "error": return response.error
}
// REQUIRED
Match.value(response).pipe(
Match.tag("success", (r) => r.data),
Match.tag("error", (r) => r.error),
Match.exhaustive
)
Type Checks (if-else → Match.when)
// FORBIDDEN
if (typeof x === "string") { ... }
else if (typeof x === "number") { ... }
// REQUIRED
Match.value(x).pipe(
Match.when(P.isString, (s) => ...),
Match.when(P.isNumber, (n) => ...),
Match.orElse(() => ...)
)
Match Completion
| Function | Returns | Use When |
|----------|---------|----------|
| Match.exhaustive | A | Compile error if cases missing |
| Match.orElse(fn) | A \| B | Fallback for remaining cases |
| Match.orElseAbsurd | A | Runtime throw if unmatched |
| Match.option | Option<A> | Match might not succeed |
| Match.either | Either<A, R> | Need success or remaining |
Schema Validation
// FORBIDDEN - manual type checking
if (typeof val === "object" && val !== null && typeof val.id === "number") { ... }
// REQUIRED - Schema.is
const UserSchema = S.Struct({ id: S.Number, name: S.String })
const isUser = S.is(UserSchema)
if (isUser(val)) { /* val is { id: number; name: string } */ }
Decision Tree
Type check (typeof/instanceof)?
└── Yes → P.is* (P.isString, P.isNumber, P.isDate, etc.)
Null/undefined check?
└── Yes → P.isNotNullable, P.isNull, P.isNotNull, etc.
Property existence check?
└── Yes → P.hasProperty(obj, "key")
Discriminated union tag check?
└── Yes → P.isTagged or Match.tag
Numeric comparison?
└── Yes → Num.lessThan, Num.between, etc.
BigInt comparison?
└── Yes → BI.lessThan, BI.sign
Date/time comparison?
└── Yes → DateTime.lessThan, DateTime.isFuture
Duration comparison?
└── Yes → Duration.lessThan, Duration.isZero
Deep equality?
└── Yes → Equal.equals(a, b)
Custom equality logic?
└── Yes → Equivalence.struct, Equivalence.array
Switch on _tag?
└── Yes → Match.tag + Match.exhaustive
If-else chain on types?
└── Yes → Match.when + predicates
Combining predicates?
└── Yes → P.and, P.or, P.not, P.every, P.some
Array filtering with type narrowing?
└── Yes → A.filter with Refinement
Validating external data?
└── Yes → S.is(schema)
Type Narrowing Reference
Refinements That Narrow Types
| Function | Narrows To |
|----------|------------|
| P.isString | string |
| P.isNumber | number |
| P.isNotNullable | NonNullable<A> |
| P.hasProperty(x, "k") | { k: unknown } |
| P.isTagged(x, "T") | { _tag: "T" } |
| P.struct({ a: P.isString }) | { a: string } |
| P.tuple(P.isString, P.isNumber) | [string, number] |
| A.filter(P.isString) | Array<string> |
| S.is(schema) | Schema's Type |
Important: P.not() Does NOT Narrow
const isNotString = P.not(P.isString)
if (isNotString(x)) {
// x is still 'unknown', NOT 'Exclude<unknown, string>'
}
Workflow
- Identify anti-patterns - Scan for typeof, instanceof, switch, if-else chains, manual null checks
- Categorize - Use decision tree to select the right Effect primitive
- Transform - Replace one pattern at a time, preserving semantics
- Add imports - Ensure required Effect imports are present
- Verify narrowing - Confirm types narrow correctly
- Check exhaustiveness - For Match, ensure all cases covered
Critical Rules
- Never leave switch statements - Always convert to Match
- Never use bare typeof/instanceof - Always use P.is* predicates
- Never use == null checks - Always use P.isNullable/P.isNotNullable
- Always use pipe for chains -
F.pipe(x, A.filter(...), A.map(...)) - Prefer Match.exhaustive - Over Match.orElse when possible
- Type narrowing matters - Choose Refinements over Predicates when narrowing needed
- P.not() doesn't narrow - Returns Predicate, not Refinement
API Reference
For complete function signatures and additional predicates, use Effect docs MCP tools to search:
- Search "Predicate isString" for predicate functions
- Search "Match exhaustive" for pattern matching
- Search "Number between" for numeric comparisons
Or read source directly:
node_modules/effect/src/Predicate.tsnode_modules/effect/src/Match.tsnode_modules/effect/src/Number.ts