Data Types
SKILL.md
Data Types in Effect
Overview
Effect provides immutable, type-safe data structures:
- Option - Represents optional values (Some/None)
- Either - Represents success/failure (Right/Left)
- Cause - Detailed failure information
- Exit - Effect execution result
- Data - Value equality for classes
- Chunk - Immutable indexed sequence
- Duration - Time spans
- DateTime - Date/time handling
Option
Represents a value that may or may not exist:
import { Option } from "effect"
const some = Option.some(42)
const none = Option.none()
const fromNull = Option.fromNullable(maybeNull)
const result = Option.match(option, {
onNone: () => "No value",
onSome: (value) => `Got: ${value}`
})
const value = Option.getOrElse(option, () => defaultValue)
const doubled = Option.map(option, (n) => n * 2)
const chained = Option.flatMap(option, (n) =>
n > 0 ? Option.some(n) : Option.none()
)
const positive = Option.filter(option, (n) => n > 0)
Option with Effect
const program = Effect.gen(function* () {
const maybeUser = yield* findUser(id)
// Convert Option to Effect
const user = yield* Option.match(maybeUser, {
onNone: () => Effect.fail(new UserNotFound()),
onSome: Effect.succeed
})
// Or use Effect.fromOption
const user = yield* maybeUser.pipe(
Effect.fromOption,
Effect.mapError(() => new UserNotFound())
)
})
Option Chaining - flatMap over Nested match
NEVER nest Option.match calls. When chaining multiple optional operations that share the same fallback, use Option.flatMap with a single Option.getOrElse:
// ❌ FORBIDDEN: Nested Option.match (pyramid of doom)
// Every onNone returns the same default — this is the signal to use flatMap
const result = pipe(
users,
Array.findFirst((u) => u.role === "admin"),
Option.match({
onNone: () => defaultName,
onSome: (admin) =>
Option.match(admin.department, {
onNone: () => defaultName,
onSome: (dept) =>
pipe(
departments,
Array.findFirst((d) => d.id === dept),
Option.match({
onNone: () => defaultName,
onSome: (d) => d.name,
}),
),
}),
}),
)
// ✅ REQUIRED: Option.flatMap chain with single getOrElse
const result = pipe(
users,
Array.findFirst((u) => u.role === "admin"),
Option.flatMap((admin) => admin.department),
Option.flatMap((dept) =>
pipe(departments, Array.findFirst((d) => d.id === dept))
),
Option.map((d) => d.name),
Option.getOrElse(() => defaultName),
)
When to use which:
| Pattern | Use When |
|---|---|
Option.match |
Converting Option to a different type (single use) |
Option.flatMap chain |
Chaining multiple optional operations with same fallback |
Option.map |
Transforming the inner value without changing Option wrapper |
Option.getOrElse |
Extracting the value with a default at the end of a chain |
Option.filter |
Adding a condition that may turn Some into None |
Either
Represents a value that is either Left (failure) or Right (success):
import { Either } from "effect"
const right = Either.right(42)
const left = Either.left("error")
const result = Either.match(either, {
onLeft: (error) => `Error: ${error}`,
onRight: (value) => `Success: ${value}`
})
const doubled = Either.map(either, (n) => n * 2)
const mapped = Either.mapLeft(either, (e) => new Error(e))
const both = Either.mapBoth(either, {
onLeft: (e) => new Error(e),
onRight: (n) => n * 2
})
const chained = Either.flatMap(either, (n) =>
n > 0 ? Either.right(n) : Either.left("negative")
)
const value = Either.getOrThrow(either)
Cause
Complete failure information for an Effect:
import { Cause } from "effect"
Cause.fail(error)
Cause.die(defect)
Cause.interrupt(id)
Cause.empty
Cause.sequential(c1, c2)
Cause.parallel(c1, c2)
Cause.isFailure(cause)
Cause.isDie(cause)
Cause.isInterrupt(cause)
const failures = Cause.failures(cause)
const defects = Cause.defects(cause)
const message = Cause.pretty(cause)
Exit
The result of running an Effect:
import { Exit } from "effect"
Exit.succeed(value)
Exit.fail(cause)
const result = Exit.match(exit, {
onFailure: (cause) => `Failed: ${Cause.pretty(cause)}`,
onSuccess: (value) => `Succeeded: ${value}`
})
Exit.isSuccess(exit)
Exit.isFailure(exit)
const value = Exit.getOrElse(exit, () => defaultValue)
const mapped = Exit.map(exit, (a) => a * 2)
Data - Value Equality
Create classes with structural equality:
import { Data, Schema } from "effect"
// Tagged class
class Person extends Data.Class<{
readonly name: string
readonly age: number
}> {}
const alice1 = new Person({ name: "Alice", age: 30 })
const alice2 = new Person({ name: "Alice", age: 30 })
alice1 === alice2 // false (reference)
Equal.equals(alice1, alice2) // true (structural)
// Tagged errors (used with Effect.fail)
// Use Schema.TaggedError for domain errors - works with Schema.is(), catchTag, and Match.tag
class UserNotFound extends Schema.TaggedError<UserNotFound>()(
"UserNotFound",
{ userId: Schema.String }
) {}
// Tagged enum
type Shape = Data.TaggedEnum<{
Circle: { radius: number }
Rectangle: { width: number; height: number }
}>
const { Circle, Rectangle } = Data.taggedEnum<Shape>()
const circle = Circle({ radius: 10 })
const rect = Rectangle({ width: 5, height: 3 })
Chunk
Immutable indexed sequence optimized for Effect:
import { Chunk } from "effect"
const chunk = Chunk.make(1, 2, 3, 4, 5)
const fromArray = Chunk.fromIterable([1, 2, 3])
const empty = Chunk.empty<number>()
const head = Chunk.head(chunk)
const tail = Chunk.tail(chunk)
const take = Chunk.take(chunk, 2)
const drop = Chunk.drop(chunk, 2)
const doubled = Chunk.map(chunk, (n) => n * 2)
const filtered = Chunk.filter(chunk, (n) => n > 2)
const sum = Chunk.reduce(chunk, 0, (acc, n) => acc + n)
const array = Chunk.toArray(chunk)
const readonlyArray = Chunk.toReadonlyArray(chunk)
Duration
Represent time spans:
import { Duration } from "effect"
const ms = Duration.millis(100)
const secs = Duration.seconds(5)
const mins = Duration.minutes(10)
const hours = Duration.hours(2)
const days = Duration.days(1)
const fromString = Duration.decode("5 seconds")
const total = Duration.sum(duration1, duration2)
const remaining = Duration.subtract(total, elapsed)
Duration.greaterThan(a, b)
Duration.lessThanOrEqualTo(a, b)
const milliseconds = Duration.toMillis(duration)
const seconds = Duration.toSeconds(duration)
DateTime
Date and time handling:
import { DateTime } from "effect"
const now = DateTime.now
const fromDate = DateTime.fromDate(new Date())
const specific = DateTime.make({
year: 2024,
month: 1,
day: 15,
hours: 10,
minutes: 30
})
const tomorrow = DateTime.add(now, { days: 1 })
const lastWeek = DateTime.subtract(now, { weeks: 1 })
const formatted = DateTime.format(now, "yyyy-MM-dd")
const utc = DateTime.setZone(now, "UTC")
const local = DateTime.setZone(now, DateTime.zoneLocal)
HashMap & HashSet
Immutable hash-based collections:
import { HashMap, HashSet } from "effect"
const map = HashMap.make(
["a", 1],
["b", 2],
["c", 3]
)
const value = HashMap.get(map, "a")
const updated = HashMap.set(map, "d", 4)
const removed = HashMap.remove(map, "a")
const set = HashSet.make(1, 2, 3, 4, 5)
const has = HashSet.has(set, 3)
const added = HashSet.add(set, 6)
const removed = HashSet.remove(set, 1)
const union = HashSet.union(set1, set2)
const intersection = HashSet.intersection(set1, set2)
Redacted
Protect sensitive values from logging:
import { Redacted } from "effect"
const apiKey = Redacted.make("sk-secret-key-123")
console.log(apiKey)
console.log(`Key: ${apiKey}`)
const actual = Redacted.value(apiKey)
Best Practices
- Use Option for nullable values - Explicit handling required
- Use Either for validation - Accumulate errors
- Use Schema.TaggedError for Effect errors - Enables catchTag and Schema.is()
- Use Chunk in streaming - Optimized for Effect operations
- Use Redacted for secrets - Prevents accidental exposure
- Use Duration for time - Type-safe time operations
Additional Resources
For comprehensive data type documentation, consult ${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt.
Search for these sections:
- "Option" for optional values
- "Either" for success/failure
- "Cause" for error details
- "Exit" for execution results
- "Data" for value equality
- "Chunk" for sequences
- "DateTime" for date handling