code-style
This skill should be used EVERY TIME you're writing TypeScript with Effect, especially when the user asks about "Effect best practices", "Effect code style", "idiomatic Effect", "functional programming", "no loops", "no for loops", "avoid imperative", "Effect Array", "Effect Record", "Effect Struct", "Effect Tuple", "Effect Predicate", "Schema-first", "Match-first", "when to use Schema", "when to use Match", "branded types", "dual APIs", "Effect guidelines", "do notation", "Effect.gen", "pipe vs method chaining", "Effect naming conventions", "Effect project structure", "data modeling in Effect", or needs to understand idiomatic Effect-TS patterns and conventions.
What this skill does
# Code Style in Effect
## Overview
Effect's idiomatic style centers on three core principles:
1. **Functional Programming Only** - No imperative logic (loops, mutation, conditionals)
2. **Schema-First Data Modeling** - Define ALL data structures as Effect Schemas
3. **Match-First Control Flow** - Define ALL conditional logic using Effect Match
Additional patterns include:
- **Branded types** - Nominal typing for primitives (built into Schema)
- **Dual APIs** - Both data-first and data-last
- **Generator syntax** - Effect.gen for readability
- **Project organization** - Layers, services, domains
## Core Principles
### 0. No Imperative Logic - Functional Programming Only
**NEVER use imperative constructs.** All code must follow functional programming principles:
- **No complex conditionals**: `else if` chains, nested `if` statements, ternary operators
- **Simple `if/else` is allowed**: A single `if` with optional `else` (no nesting, no `else if`)
- **`switch/case` as last resort**: Prefer `Match.type`/`Match.value`, but `switch` is acceptable when Match doesn't fit
- **No loops**: `for`, `while`, `do...while`, `for...of`, `for...in`
- **No mutation**: Reassignment, push/pop/splice, property mutation
**Use instead:**
- Pattern matching (`Match`, `Option.match`, `Either.match`, `Array.match`)
- Effect's `Array` module (`Array.map`, `Array.filter`, `Array.reduce`, `Array.flatMap`, `Array.filterMap`, etc.)
- Effect's `Record` module (`Record.map`, `Record.filter`, `Record.get`, `Record.keys`, `Record.values`, etc.)
- Effect's `Struct` module (`Struct.pick`, `Struct.omit`, `Struct.evolve`, `Struct.get`, etc.)
- Effect's `Tuple` module (`Tuple.make`, `Tuple.getFirst`, `Tuple.mapBoth`, etc.)
- Effect's `Predicate` module (`Predicate.and`, `Predicate.or`, `Predicate.not`, `Predicate.struct`, etc.)
- Effect combinators (`Effect.forEach`, `Effect.all`, `Effect.reduce`)
- Effect's `Function` module (`pipe`, `flow`, `identity`, `constant`, `compose`)
- Recursion for complex iteration
- First-class functions (pass functions as arguments, return functions)
#### Conditionals - Use Pattern Matching
- **Simple `if/else`** → Allowed (no nesting, no `else if`)
- **`else if` chains** → `Match.value` + `Match.when` (FORBIDDEN as `else if`)
- **Nested `if` statements** → Flatten with early return, or `Match.value` + `Match.when`
- **`switch/case` statements** → Prefer `Match.type` + `Match.tag`, but `switch` is acceptable
- **Ternary operators (`? :`)** → `Match.value` + `Match.when` or simple `if/else`
- **Single optional value** → `Option.match`
- **Chained optional operations** → `Option.flatMap` + `Option.getOrElse`
- **Result/error conditionals** → `Either.match` or `Effect.match`
```typescript
// ✅ ALLOWED: Simple if/else (not nested, no else if)
if (user.isAdmin) {
return grantFullAccess()
}
return grantLimitedAccess()
// ✅ ALLOWED: Simple if with else
if (isValid) {
process(data)
} else {
handleError()
}
// ❌ FORBIDDEN: else if (use Match instead)
if (user.role === "admin") {
return "full access"
} else if (user.role === "user") {
return "limited access"
} else {
return "no access"
}
// ❌ FORBIDDEN: Nested if
if (user.isActive) {
if (user.isAdmin) {
return "active admin"
}
}
// ❌ FORBIDDEN: ternary
const message = isError ? "Failed" : "Success"
// ❌ FORBIDDEN: direct ._tag access
if (event._tag === "UserCreated") { ... }
const isCreated = event._tag === "UserCreated"
// ❌ FORBIDDEN: ._tag in type definitions
type ConflictTag = Conflict["_tag"] // Never extract _tag as a type
// ❌ FORBIDDEN: ._tag in array predicates
const hasConflict = conflicts.some((c) => c._tag === "MergeConflict")
const mergeConflicts = conflicts.filter((c) => c._tag === "MergeConflict")
const countMerge = conflicts.filter((c) => c._tag === "MergeConflict").length
// ✅ REQUIRED: Schema.is() as predicate
const hasConflict = conflicts.some(Schema.is(MergeConflict))
const mergeConflicts = conflicts.filter(Schema.is(MergeConflict))
const countMerge = conflicts.filter(Schema.is(MergeConflict)).length
// ✅ REQUIRED: Match.value for else-if replacement
const getAccess = (user: User) =>
Match.value(user.role).pipe(
Match.when("admin", () => "full access"),
Match.when("user", () => "limited access"),
Match.orElse(() => "no access")
)
// ✅ REQUIRED: Match.type for multi-case
const getStatusMessage = Match.type<Status>().pipe(
Match.when("pending", () => "waiting"),
Match.when("active", () => "running"),
Match.exhaustive
)
// ✅ ALLOWED: switch as last resort (prefer Match)
switch (status) {
case "pending": return "waiting"
case "active": return "running"
default: return "unknown"
}
// ✅ REQUIRED: Option.match for single optional
const displayName = Option.match(maybeUser, {
onNone: () => "Guest",
onSome: (user) => user.name
})
// ❌ FORBIDDEN: Nested Option.match (pyramid of doom)
// Signal: every onNone returns the same default
const name = pipe(
findUser(id),
Option.match({
onNone: () => "Unknown",
onSome: (user) =>
Option.match(user.profile, {
onNone: () => "Unknown",
onSome: (profile) => profile.displayName,
}),
}),
)
// ✅ REQUIRED: Option.flatMap chain for multiple optionals
const name = pipe(
findUser(id),
Option.flatMap((user) => user.profile),
Option.map((profile) => profile.displayName),
Option.getOrElse(() => "Unknown"),
)
// ✅ REQUIRED: Either.match for results
const result = Either.match(parseResult, {
onLeft: (error) => `Error: ${error}`,
onRight: (value) => `Success: ${value}`
})
// ✅ REQUIRED: Match.tag for discriminated unions (not ._tag access)
const handleEvent = Match.type<AppEvent>().pipe(
Match.tag("UserCreated", (e) => notifyAdmin(e.userId)),
Match.tag("UserDeleted", (e) => cleanupData(e.userId)),
Match.exhaustive
)
// ✅ REQUIRED: Schema.is() for type guards on Schema types (Schema.TaggedClass)
if (Schema.is(UserCreated)(event)) {
// event is narrowed to UserCreated
}
// Schema.TaggedError works with Schema.is(), Effect.catchTag, and Match.tag.
// Always use Schema.TaggedError for domain errors.
```
**When you encounter `else if` chains, nested `if` statements, or ternary operators in existing code, refactor them immediately.** Simple `if/else` is acceptable.
#### Loops - Use Effect's Array Module and Recursion
**NEVER use `for`, `while`, `do...while`, `for...of`, or `for...in` loops.** Use Effect's functional alternatives.
**Why Effect's Array module over native Array methods:**
- `Array.findFirst` returns `Option<A>` instead of `A | undefined`
- `Array.get` returns `Option<A>` for safe indexing
- `Array.filterMap` combines filter and map in one pass
- `Array.partition` returns typed tuple `[excluded, satisfying]`
- `Array.groupBy` returns `Record<K, NonEmptyArray<A>>`
- `Array.match` provides exhaustive empty/non-empty handling
- All functions work with `pipe` for composable pipelines
- Consistent dual API (data-first and data-last)
```typescript
import { Array, pipe } from "effect";
// ❌ FORBIDDEN: for loop
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
// ❌ FORBIDDEN: for...of loop
const results = [];
for (const item of items) {
results.push(process(item));
}
// ❌ FORBIDDEN: while loop
let sum = 0;
let i = 0;
while (i < numbers.length) {
sum += numbers[i];
i++;
}
// ❌ FORBIDDEN: forEach with mutation
const output = [];
items.forEach((item) => output.push(transform(item)));
// ✅ REQUIRED: Array.map for transformation
const doubled = Array.map(numbers, (n) => n * 2);
// or with pipe
const doubled = pipe(
numbers,
Array.map((n) => n * 2),
);
// ✅ REQUIRED: Array.filter for selection
const adults = Array.filter(users, (u) => u.age >= 18);
// ✅ REQUIRED: Array.reduce for accumulation
const sum = Array.reduce(numbers, 0, (acc, n) => acc + n);
// ✅ REQUIRED: Array.flatMap for one-to-many
const allTags = Array.flatMap(posts, (post) => post.tags);
// ✅ REQUIRED: Array.fRelated in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.