一键导入
effect-collections
Effect collection patterns - Arrays, Chunks, Records, HashMaps. Use when choosing collection types or working with transformations.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Effect collection patterns - Arrays, Chunks, Records, HashMaps. Use when choosing collection types or working with transformations.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Effect service definition, interface design, error types, retries. Use when creating new services or defining error hierarchies.
AXM - Agent Extension Manager: Use for any operation (install/create/new/edit/update/add/remove/delete/publish/find/discover) on agent skills, subagents, slash commands/stored prompts, MCP servers, context packages, rule extensions, hook extensions, or packs — e.g. "create a skill", "make a /command", "add a subagent", "build an MCP server", "publish an extension". Use this BEFORE hand-authoring or editing any SKILL.md, slash-command, subagent, MCP, rule, hook, or extension manifest file: route extension authoring through AXM instead of writing these files directly.
Native skill manifest with two unknown top-level keys.
Effect CLI + Effect architecture. Use when adding commands, defining flags, or wiring handlers. Covers file organization, argument/flag patterns, and testing.
"unterminated
Native skill with an invalid extension name in manifest.
| name | effect-collections |
| description | Effect collection patterns - Arrays, Chunks, Records, HashMaps. Use when choosing collection types or working with transformations. |
| user-invocable | false |
Use native arrays with Effect's Array module for most transformations. Reserve Chunk for repeated concatenation and Streams. Prefer native objects with Record module for string-keyed data. Choose HashMap only for complex keys or value-based equality.
Effect's Array module operates on standard JavaScript arrays—zero runtime overhead with composable operations that understand Option/Either:
import { Array, Option, pipe } from "effect";
// filterMap: combined filter + map using Option
const evenSquares = Array.filterMap([1, 2, 3, 4], (x) =>
x % 2 === 0 ? Option.some(x * x) : Option.none(),
); // [4, 16]
// separate: partitions Either array into [lefts, rights]
const [errors, values] = Array.separate(results);
// getSomes: extracts values from Option array
const defined = Array.getSomes([Option.some(1), Option.none(), Option.some(2)]);
// [1, 2]
// head/last return Option
const first = Array.head(items); // Option<T>
| Function | Purpose |
|---|---|
filterMap | Filter + map with Option in one pass |
traverse | Effectful mapping |
separate | Split Either[] into [lefts, rights] |
partition | Split by predicate |
getSomes/getLefts/getRights | Extract from wrapped types |
groupBy | Group into Record<string, NonEmptyArray> |
head/last | Safe access returning Option |
// Point-free pipelines
const getActiveNames = flow(
Array.filter((u: User) => u.active),
Array.map((u) => u.name),
Array.take(10),
);
// Effectful iteration
yield * Effect.forEach(users, (user) => Effect.log(`Processing ${user.name}`));
// Array comprehensions with Do
const pairs = pipe(
Array.Do,
Array.bind("x", () => [1, 2, 3]),
Array.bind("y", () => ["a", "b"]),
Array.map(({ x, y }) => `${x}${y}`),
); // ["1a", "1b", "2a", "2b", "3a", "3b"]
Chunk<T> is optimized specifically for amortizing repeated concatenation cost—O(1) amortized vs O(n) for native arrays. Essential when building collections incrementally; slower than arrays for everything else.
Benchmark: Building 100,000 elements via repeated concat: ~52s (native array) vs ~10.7ms (Chunk)—~5000x faster.
import { Chunk, Equal } from "effect";
// O(1) concatenation
const combined = Chunk.appendAll(chunk1, chunk2);
const withItem = Chunk.append(chunk, 4);
// Creating
const chunk = Chunk.make(1, 2, 3);
const fromArray = Chunk.fromIterable([1, 2, 3]); // clones
const unsafe = Chunk.unsafeFromArray(array); // no clone—dangerous
// Converting back
const array = Chunk.toReadonlyArray(chunk);
// Built-in structural equality
Equal.equals(chunk1, chunk2);
| Use Chunk | Use Native Arrays |
|---|---|
| Building collections with repeated concat | Simple one-time transformations |
Working with Streams (runCollect) | Small arrays |
| Batches combined repeatedly | Interfacing with external APIs |
| Concurrent contexts (guaranteed immutable) | Performance-critical paths without concat |
Like Array, the Record module operates on native JavaScript objects without wrapping:
import { Record, Option, pipe } from "effect";
// Safe access returning Option
const port = Record.get(config, "port"); // Option<number>
// Functional transformations (return new objects)
const doubled = Record.map(scores, (n) => n * 2);
const filtered = Record.filter(config, (v) => v !== null);
const asArray = Record.collect(users, (id, user) => `${id}: ${user.name}`);
// Creating from iterables
const byId = Record.fromIterableBy(users, (user) => user.id);
const fromPairs = Record.fromEntries([
["a", 1],
["b", 2],
]);
// Set operations
const merged = Record.union(defaults, overrides, (a, b) => b);
const common = Record.intersection(obj1, obj2, (a, b) => a + b);
HashMap<K, V> supports any key type and compares keys by value when using Data.struct:
import { HashMap, Data, Equal } from "effect";
// Complex object keys with value-based equality
const key1 = Data.struct({ x: 1, y: 2 });
const key2 = Data.struct({ x: 1, y: 2 });
const map = HashMap.make([key1, "first"], [key2, "second"]);
HashMap.size(map); // 1—keys equal by value, second overwrites first
// Immutable operations
const updated = HashMap.set(map, key1, "updated");
const removed = HashMap.remove(map, key1);
// Safe access
const value = HashMap.get(map, key1); // Option<string>
// Batch mutations for performance
const efficient = HashMap.mutate(HashMap.empty(), (draft) => {
HashMap.set(draft, "a", 1);
HashMap.set(draft, "b", 2);
});
| Feature | Native Objects + Record | HashMap |
|---|---|---|
| Key types | string | symbol only | Any type with Hash + Equal |
| Key equality | Reference-based | Value-based (with Data.struct) |
| Mutability | Mutable by default | Immutable (runtime enforced) |
| JSON serialization | Direct | Requires conversion |
| Performance | V8-optimized | HAMT-based, slight overhead |
| Scenario | Use |
|---|---|
| Simple transforms (map, filter) | Native arrays + Array module |
| Working with Option/Either in arrays | Array module (filterMap, getSomes) |
| Building with repeated concat | Chunk |
| Stream processing | Chunk (native to Streams) |
| JSON serialization, external APIs | Native arrays |
| Scenario | Use |
|---|---|
| String-keyed config/data | Native objects + Record module |
| Functional transforms on objects | Record module |
| Complex object keys | HashMap with Data.struct |
| Value-based key equality | HashMap |
| JSON serialization, external APIs | Native objects |
Effect provides type aliases that signal immutability at the type level. Use these in function signatures and type definitions.
| Effect Type | Equivalent To | Notes |
|---|---|---|
Array.Array<T> | ReadonlyArray<T> | Same type, re-exported by Effect |
readonly T[] | ReadonlyArray<T> | TypeScript shorthand |
Record.ReadonlyRecord<K,V> | { readonly [P in K]: V } | Effect's readonly record type |
import { Array, Record } from "effect";
// Prefer Effect's type aliases in signatures
interface UserState {
readonly users: Array.Array<User>; // = ReadonlyArray<User>
readonly byId: Record.ReadonlyRecord<string, User>; // readonly string-keyed
}
// Function signatures signal immutability
const processUsers = (users: Array.Array<User>): Array.Array<ProcessedUser> =>
Array.map(users, transform);
// All three are equivalent for arrays:
type A = Array.Array<string>; // Effect alias (preferred)
type B = ReadonlyArray<string>; // TypeScript built-in
type C = readonly string[]; // TypeScript shorthand
Chunk and HashMap are already immutable—no readonly variants needed:
// Chunk operations always return new Chunks
const chunk2 = Chunk.append(chunk1, item); // chunk1 unchanged
// HashMap operations always return new HashMaps
const map2 = HashMap.set(map1, key, value); // map1 unchanged
import { Schema } from "effect";
// Array schemas
const UserList = Schema.Array(
Schema.Struct({
id: Schema.Number,
name: Schema.String,
}),
);
// Record schemas
const ScoreMap = Schema.Record({ key: Schema.String, value: Schema.Number });
// HashMap schemas
const HashMapSchema = Schema.HashMap({
key: Schema.String,
value: Schema.Number,
});
.filter(Option.isSome).map(...) with Array.getSomes// Before: verbose filter/map chain
const agents = args.agent
.map((id) => getAgentById(id))
.filter(Option.isSome)
.map((opt) => opt.value);
// After: single-pass extraction
const agents = pipe(
args.agent,
Array.map((id) => getAgentById(id)),
Array.getSomes,
);
.find() with Array.findFirst (returns Option)// Before: nullable result requires null checks
const skill = skills.find((s) => s.name === args.skill);
if (!skill) throw new Error("Not found");
// After: explicit Option handling
const skill = pipe(
skills,
Array.findFirst((s) => s.name === args.skill),
Option.getOrThrowWith(() => new Error("Not found")),
);
// Or with Option.match for safe handling
const result = pipe(
skills,
Array.findFirst((s) => s.name === args.skill),
Option.match({
onNone: () => defaultSkill,
onSome: (skill) => skill,
}),
);
Array.head/Array.get// Before: unsafe access even after length check
if (refs.length > 0) {
const ref = refs[0]; // still typed as T | undefined
}
// After: safe access with Option
const ref = Array.head(refs); // Option<T>
const result = Option.match(ref, {
onNone: () => handleEmpty(),
onSome: (r) => process(r),
});
// For arbitrary index
const third = Array.get(items, 2); // Option<T>
Array.partition// Before: calls getAgentById twice per element
const validIds = args.agent.filter((id) => Option.isSome(getAgentById(id)));
const invalidIds = args.agent.filter((id) => Option.isNone(getAgentById(id)));
// After: single pass with partition
const [invalidIds, validIds] = Array.partition(args.agent, (id) => Option.isSome(getAgentById(id)));
.map().filter().map() with Array.filterMap// Before: multiple passes
const indices = options
.map((opt, i) => (opt.selected ? i : undefined))
.filter((i) => i !== undefined)
.map((i) => i!);
// After: single pass with Option
const indices = Array.filterMap(options, (opt, i) =>
opt.selected ? Option.some(i) : Option.none(),
);
// Before: destructuring loses type safety
const [resolver, ...rest] = remaining;
if (!resolver) throw new Error("Empty");
// After: explicit head/tail with NonEmpty guarantees
const head = Array.head(remaining);
const tail = Array.tail(remaining);
// Or use NonEmpty variants when you know the array isn't empty
const head = Array.headNonEmpty(remaining); // T (not Option<T>)
const tail = Array.tailNonEmpty(remaining); // Array<T>
filterMap, getSomes, separate for Option/Eitherget, filterMap, collect for object transformsArray.Array<T> and Record.ReadonlyRecord<K,V> in signaturesArray.head, Array.get, Array.findFirst instead of [0], .find()Array.partition instead of double filter, Array.filterMap instead of map/filter chains