traits
Traits in Effect
Overview
Effect provides trait-based abstractions for:
- Equal - Structural equality comparison
- Hash - Hash code generation
- Equivalence - Custom equality relations
- Order - Ordering and comparison
These enable consistent behavior across Effect's data structures.
Equal - Structural Equality
The Equal Interface
import { Equal } from "effect";
Equal.equals(a, b);
import { Data } from "effect";
class Person extends Data.Class<{
name: string;
age: number;
}> {}
const p1 = new Person({ name: "Alice", age: 30 });
const p2 = new Person({ name: "Alice", age: 30 });
Equal.equals(p1, p2);
p1 === p2;
Implementing Equal (Recommended: Use Data.Class)
PREFER Data.Class which provides Equal and Hash automatically:
import { Data, Equal } from "effect";
// Data.Class provides Equal and Hash automatically
class Point extends Data.Class<{ readonly x: number; readonly y: number }> {}
const p1 = new Point({ x: 1, y: 2 });
const p2 = new Point({ x: 1, y: 2 });
Equal.equals(p1, p2);
For Schema types, use Schema.Class which also provides Equal:
import { Schema, Equal } from "effect";
class Point extends Schema.Class<Point>("Point")({
x: Schema.Number,
y: Schema.Number,
}) {}
const p1 = new Point({ x: 1, y: 2 });
const p2 = new Point({ x: 1, y: 2 });
Equal.equals(p1, p2); // true
Why Equal Matters
Effect's collections (HashMap, HashSet) use Equal for lookups:
import { HashMap, Data } from "effect";
class UserId extends Data.Class<{ id: string }> {}
const map = HashMap.make([
[new UserId({ id: "1" }), "Alice"],
[new UserId({ id: "2" }), "Bob"],
]);
// Works because UserId implements Equal
HashMap.get(map, new UserId({ id: "1" })); // Option.some("Alice")
Hash - Hash Code Generation
The Hash Interface
import { Hash } from "effect";
Hash.hash(value);
Hash.string("hello");
Hash.number(42);
Hash.combine(h1)(h2);
Hash.array([1, 2, 3]);
Hash Contract
Objects that are Equal MUST have the same hash:
// If Equal.equals(a, b) === true
// Then Hash.hash(a) === Hash.hash(b)
// The reverse is NOT required:
// Same hash does NOT mean equal (hash collisions are allowed)
Hash with Data.Class (Recommended)
Data.Class provides both Equal and Hash automatically:
import { Data, Hash, Equal } from "effect";
class Rectangle extends Data.Class<{
readonly width: number;
readonly height: number;
}> {}
const r1 = new Rectangle({ width: 10, height: 5 });
const r2 = new Rectangle({ width: 10, height: 5 });
Equal.equals(r1, r2);
Hash.hash(r1) === Hash.hash(r2);
Equivalence - Custom Equality Relations
Creating Equivalences
import { Equivalence } from "effect";
const caseInsensitive = Equivalence.make<string>((a, b) => a.toLowerCase() === b.toLowerCase());
caseInsensitive("Hello", "hello");
caseInsensitive("Hello", "HELLO");
Equivalence.string;
Equivalence.number;
Equivalence.boolean;
Equivalence.strict;
Transforming Equivalences
// Map to compare by derived value
const byLength = Equivalence.string.pipe(Equivalence.mapInput((s: string) => s.length.toString()));
// Actually, better approach:
const byLength = Equivalence.make<string>((a, b) => a.length === b.length);
// Combine equivalences
const userEquivalence = Equivalence.make<User>(
(a, b) => Equivalence.string(a.name, b.name) && Equivalence.number(a.age, b.age),
);
Using with Collections
import { Array as Arr } from "effect";
const users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "alice", age: 30 }, // Same as Alice?
];
// Dedupe with custom equivalence
const byNameIgnoreCase = Equivalence.make<User>((a, b) => a.name.toLowerCase() === b.name.toLowerCase());
const unique = Arr.dedupe(users); // Uses default equality
const uniqueByName = Arr.dedupeWith(users, byNameIgnoreCase);
Order - Comparison and Sorting
The Order Interface
import { Order } from "effect";
Order.string("a", "b");
Order.string("b", "a");
Order.string("a", "a");
Order.lessThan(Order.number)(5, 10);
Order.greaterThan(Order.number)(5, 10);
Order.lessThanOrEqualTo(Order.number)(5, 5);
Order.between(Order.number)({ minimum: 0, maximum: 10 })(5);
Order.min(Order.number)(3, 7);
Order.max(Order.number)(3, 7);
Order.clamp(Order.number)({ minimum: 0, maximum: 100 })(150);
Built-in Orders
Order.string;
Order.number;
Order.boolean;
Order.bigint;
Order.Date;
Creating Custom Orders
const byAge = Order.make<Person>((a, b) => (a.age < b.age ? -1 : a.age > b.age ? 1 : 0));
const byName = Order.mapInput(Order.string, (p: Person) => p.name);
const byAge = Order.mapInput(Order.number, (p: Person) => p.age);
Combining Orders
const byAgeThenName = Order.combine(byAge, byName);
const byAgeDesc = Order.reverse(byAge);
const sortUsers = Order.combine(
Order.mapInput(Order.string, (u: User) => u.department),
Order.combine(
Order.reverse(Order.mapInput(Order.number, (u: User) => u.salary)),
Order.mapInput(Order.string, (u: User) => u.name),
),
);
Sorting with Order
import { Array as Arr } from "effect";
const people = [
{ name: "Charlie", age: 35 },
{ name: "Alice", age: 30 },
{ name: "Bob", age: 30 },
];
const byAge = Arr.sort(
people,
Order.mapInput(Order.number, (p) => p.age),
);
const sorted = Arr.sort(
people,
Order.combine(
Order.mapInput(Order.number, (p) => p.age),
Order.mapInput(Order.string, (p) => p.name),
),
);
Data.Class - Automatic Implementation
Using Data.Class automatically implements Equal and Hash:
import { Data, Equal, Hash } from "effect";
class User extends Data.Class<{
id: string;
name: string;
email: string;
}> {}
const u1 = new User({ id: "1", name: "Alice", email: "a@example.com" });
const u2 = new User({ id: "1", name: "Alice", email: "a@example.com" });
Equal.equals(u1, u2); // true
Hash.hash(u1) === Hash.hash(u2); // true
Best Practices
- Use Data.Class for value objects - Automatic Equal/Hash
- Implement both Equal and Hash together - Required for collections
- Use Order.mapInput for simple ordering - Cleaner than manual comparison
- Combine orders for complex sorting - More composable than custom compare
- Consider Equivalence for custom equality - When default equality isn't enough
Additional Resources
For comprehensive trait documentation, consult ${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt.
Search for these sections:
- "Equal" for equality comparison
- "Hash" for hash code generation
- "Equivalence" for custom equality relations
- "Order" for ordering and comparison
More from andrueandersoncs/claude-skill-effect-ts
schema
This skill should be used when the user asks about "Effect Schema", "Schema.Struct", "Schema.decodeUnknown", "data validation", "parsing", "Schema.transform", "Schema filters", "Schema annotations", "JSON Schema", "Schema.Class", "Schema branded types", "encoding", "decoding", "Schema.parseJson", or needs to understand how Effect handles data validation and transformation.
13testing
This skill should be used when the user asks about "Effect testing", "@effect/vitest", "it.effect", "it.live", "it.scoped", "it.layer", "it.prop", "Schema Arbitrary", "property-based testing", "fast-check", "TestClock", "testing effects", "mocking services", "test layers", "TestContext", "Effect.provide test", "time testing", "Effect test utilities", "unit testing Effect", "generating test data", "flakyTest", "test coverage", "100% coverage", "service testing", "test doubles", "mock services", or needs to understand how to test Effect-based code.
13configuration
This skill should be used when the user asks about "Effect Config", "environment variables", "configuration management", "Config.string", "Config.number", "ConfigProvider", "Config.nested", "Config.withDefault", "Config.redacted", "sensitive values", "config validation", "loading config from JSON", "config schema", or needs to understand how Effect handles application configuration.
12concurrency
This skill should be used when the user asks about "Effect concurrency", "fibers", "Fiber", "forking", "Effect.fork", "Effect.forkDaemon", "parallel execution", "Effect.all concurrency", "Deferred", "Queue", "PubSub", "Semaphore", "Latch", "fiber interruption", "Effect.race", "Effect.raceAll", "concurrent effects", or needs to understand how Effect handles parallel and concurrent execution.
11observability
This skill should be used when the user asks about "Effect logging", "Effect.log", "Effect metrics", "Effect tracing", "spans", "telemetry", "Metric.counter", "Metric.gauge", "Metric.histogram", "OpenTelemetry", "structured logging", "log levels", "Effect.logDebug", "Effect.logInfo", "Effect.logWarning", "Effect.logError", or needs to understand how Effect handles logging, metrics, and distributed tracing.
10streams
This skill should be used when the user asks about "Effect Stream", "Stream.from", "Stream.map", "Stream.filter", "Stream.run", "streaming data", "async iteration", "Sink", "Channel", "Stream.concat", "Stream.merge", "backpressure", "Stream.fromIterable", "chunked processing", "real-time data", or needs to understand how Effect handles streaming data processing.
10