Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Expert skill for designing and implementing algebraic effect systems including effect annotation, inference, handlers, polymorphism, and row-based effect typing.
.claude/skills/a5c-ai-effect-systems/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 62% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 0% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 86% | 0% |
Design and implement algebraic effect systems for tracking and handling computational effects in programming languages.
Invoke this skill when you need to:
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | effectModel | string | Yes | Model (algebraic, monadic, capability) | | inferenceStrategy | string | Yes | Strategy (annotated, inferred, mixed) | | features | array | No | Features to implement | | builtinEffects | array | No | Built-in effects to include |
json{ "effectModel": "algebraic", // Koka/Eff style "effectModel": "monadic", // Haskell IO style "effectModel": "capability" // Capability-based }
json{ "features": [ "effect-inference", "effect-handlers", "effect-polymorphism", "effect-rows", "effect-subtyping", "effect-abstraction", "resumption-control", "multi-shot-continuations" ] }
effect-system/
├── syntax/
│ ├── effect-annotation.grammar # Effect annotation syntax
│ ├── effect-handler.grammar # Handler syntax
│ └── effect-operation.grammar # Operation syntax
├── typing/
│ ├── effect-types.ts # Effect type definitions
│ ├── effect-inference.ts # Effect inference
│ ├── effect-checking.ts # Effect checking
│ └── effect-rows.ts # Row polymorphism
├── handlers/
│ ├── handler-impl.ts # Handler implementation
│ ├── continuation.ts # Continuation management
│ └── resumption.ts # Resumption handling
├── runtime/
│ ├── effect-runtime.ts # Runtime effect support
│ └── builtin-effects.ts # Built-in effects
└── tests/
├── inference.test.ts
├── handlers.test.ts
└── polymorphism.test.tstypescript// Effect declaration effect State<S> { get(): S put(s: S): () } effect Exception<E> { raise(e: E): Nothing } // Effect types type EffectType = { operations: Map<string, OperationType>; } interface OperationType { name: string; params: Type[]; result: Type; } // Function types with effects interface FunctionType { params: Type[]; result: Type; effects: EffectRow; } // Effect rows (for polymorphism) type EffectRow = | { type: 'empty' } | { type: 'single'; effect: EffectType } | { type: 'union'; effects: EffectType[] } | { type: 'variable'; name: string } // Effect polymorphism | { type: 'extend'; base: EffectRow; effect: EffectType };
typescript// Handler syntax handle expr with { return(x) -> returnClause(x), get() -> getClause(resume), put(s) -> putClause(s, resume) } // Handler representation interface Handler { effect: EffectType; returnClause: (value: any) => any; operationClauses: Map<string, OperationClause>; } interface OperationClause { operation: string; params: string[]; resumeName: string; body: Expr; } // Handler typing // handle[E] : (() -E> A) -> ((A -> B) & Handler[E]) -> B function typeHandler( expr: Expr, handler: Handler, env: TypeEnv ): { resultType: Type; remainingEffects: EffectRow } { const exprType = inferType(expr, env); // Check that handler handles the effect checkHandlerCovers(handler, exprType.effects); // Result type comes from handler clauses const resultType = inferHandlerResult(handler, exprType.result, env); // Remove handled effect from row const remainingEffects = removeEffect(exprType.effects, handler.effect); return { resultType, remainingEffects }; }
typescript// Effect inference algorithm function inferEffects(expr: Expr, env: TypeEnv): InferResult { switch (expr.type) { case 'var': return { type: lookupType(env, expr.name), effects: emptyRow() }; case 'lambda': const bodyResult = inferEffects(expr.body, extendEnv(env, expr.param, expr.paramType)); return { type: { type: 'function', param: expr.paramType, result: bodyResult.type, effects: bodyResult.effects }, effects: emptyRow() // Lambda itself is pure }; case 'app': const fnResult = inferEffects(expr.fn, env); const argResult = inferEffects(expr.arg, env); const fnType = fnResult.type as FunctionType; return { type: fnType.result, effects: unionRows(fnResult.effects, argResult.effects, fnType.effects) }; case 'perform': const opType = lookupOperation(env, expr.effect, expr.operation); const argEffects = expr.args.map(a => inferEffects(a, env).effects); return { type: opType.result, effects: unionRows(singleRow(expr.effect), ...argEffects) }; case 'handle': const exprResult = inferEffects(expr.body, env); const handlerResult = checkHandler(expr.handler, exprResult, env); return handlerResult; // ... other cases } }
typescript// Effect-polymorphic function // map : forall E. (a -E> b) -> List a -E> List b interface EffectPolymorphicType { effectVars: string[]; typeVars: string[]; type: Type; } // Row polymorphism for effects // foo : () -<State, E>-> Int (E is a row variable) type RowVariable = { type: 'rowVar'; name: string }; function unifyRows(row1: EffectRow, row2: EffectRow): Substitution { // Row unification algorithm if (row1.type === 'variable') { return { [row1.name]: row2 }; } if (row2.type === 'variable') { return { [row2.name]: row1 }; } if (row1.type === 'empty' && row2.type === 'empty') { return {}; } // Handle union and extend cases... }
typescript// Delimited continuations for effect handlers interface Continuation<A, B> { resume(value: A): B; } // Multi-shot continuations (can resume multiple times) interface MultiShotContinuation<A, B> extends Continuation<A, B> { clone(): MultiShotContinuation<A, B>; } // One-shot continuations (can only resume once) interface OneShotContinuation<A, B> extends Continuation<A, B> { readonly consumed: boolean; } // Runtime continuation capture class ContinuationCapture { capture<A, B>( prompt: Prompt, body: (k: Continuation<A, B>) => B ): B { // Capture the current continuation up to prompt const k = captureDelimited(prompt); return body(k); } }
typescript// Common built-in effects const builtinEffects = { IO: { operations: { print: { params: [StringType], result: UnitType }, readLine: { params: [], result: StringType }, readFile: { params: [StringType], result: StringType }, writeFile: { params: [StringType, StringType], result: UnitType } } }, State: { typeParams: ['S'], operations: { get: { params: [], result: TypeVar('S') }, put: { params: [TypeVar('S')], result: UnitType } } }, Exception: { typeParams: ['E'], operations: { raise: { params: [TypeVar('E')], result: NothingType } } }, Async: { operations: { await: { params: [PromiseType(TypeVar('A'))], result: TypeVar('A') }, spawn: { params: [FunctionType([], TypeVar('A'), AsyncEffect)], result: TaskType(TypeVar('A')) } } }, NonDet: { operations: { choice: { params: [], result: BoolType }, fail: { params: [], result: NothingType } } } };
typescript// Pure functions can be optimized more aggressively function canOptimize(fn: FunctionType): OptimizationLevel { if (isEmptyRow(fn.effects)) { return 'pure'; // Full optimization: CSE, memoization, parallelization } if (onlyReads(fn.effects)) { return 'read-only'; // Can reorder, CSE } if (isLocalState(fn.effects)) { return 'local-state'; // Can inline, but not reorder } return 'effectful'; // Limited optimization } // Effect-based dead code elimination function eliminateDeadCode(expr: Expr): Expr { const effects = inferEffects(expr); if (isEmptyRow(effects) && !isUsed(expr)) { return unit; // Pure unused expression can be eliminated } return expr; }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 51,162 | 42,627 | -17% | 1 | 1 | 0% | 8,256 | 11,028 | +34% | 0 | 0 | — |
case-02 | fail→fail | 40,617 | 35,157 | -13% | 1 | 1 | 0% | 8,249 | 11,021 | +34% | 0 | 0 | — |
case-03 | fail→fail | 49,927 | 46,621 | -7% | 1 | 1 | 0% | 8,258 | 11,030 | +34% | 0 | 0 | — |
case-04 | fail→fail | 19,847 | 20,538 | +3% | 1 | 1 | 0% | 3,591 | 6,633 | +85% | 0 | 0 | — |
case-05 | pass→pass | 22,047 | 33,392 | +51% | 1 | 1 | 0% | 3,959 | 8,072 | +104% | 0 | 0 | — |
case-06 | pass→pass | 15,528 | 14,453 | -7% | 1 | 1 | 0% | 3,112 | 5,556 | +79% | 0 | 0 | — |
case-07 | fail→pass | 39,576 | 33,098 | -16% | 1 | 1 | 0% | 6,719 | 9,566 | +42% | 0 | 0 | — |
case-08 | fail→fail | 21,487 | 16,855 | -22% | 1 | 1 | 0% | 3,908 | 5,877 | +50% | 0 | 0 | — |
case-09 | pass→pass | 12,412 | 11,224 | -10% | 1 | 1 | 0% | 2,250 | 4,548 | +102% | 0 | 0 | — |
case-10 | pass→pass | 29,915 | 38,169 | +28% | 1 | 1 | 0% | 5,563 | 10,991 | +98% | 0 | 0 | — |
case-11 | fail→pass | 21,693 | 17,873 | -18% | 1 | 1 | 0% | 3,787 | 6,135 | +62% | 0 | 0 | — |
case-12 | pass→pass | 22,948 | 15,872 | -31% | 1 | 1 | 0% | 3,356 | 5,697 | +70% | 0 | 0 | — |
case-13 | fail→fail | 13,305 | 10,881 | -18% | 1 | 1 | 0% | 2,324 | 4,875 | +110% | 0 | 0 | — |
case-14 | fail→fail | 17,161 | 14,705 | -14% | 1 | 1 | 0% | 2,802 | 5,457 | +95% | 0 | 0 | — |
case-15 | pass→fail | 14,163 | 16,187 | +14% | 1 | 1 | 0% | 2,418 | 5,013 | +107% | 0 | 0 | — |
case-16 | fail→pass | 46,370 | 33,821 | -27% | 1 | 1 | 0% | 8,212 | 8,249 | +0% | 0 | 0 | — |
case-17 | fail→pass | 16,934 | 11,364 | -33% | 1 | 1 | 0% | 2,583 | 4,784 | +85% | 0 | 0 | — |
case-18 | fail→fail | 14,156 | 11,781 | -17% | 1 | 1 | 0% | 2,649 | 5,028 | +90% | 0 | 0 | — |
case-19 | pass→pass | 23,623 | 14,988 | -37% | 1 | 1 | 0% | 4,540 | 5,643 | +24% | 0 | 0 | — |
case-20 | pass→pass | 14,730 | 14,535 | -1% | 1 | 1 | 0% | 2,402 | 5,066 | +111% | 0 | 0 | — |
case-21 | pass→pass | 28,402 | 29,184 | +3% | 1 | 1 | 0% | 4,736 | 7,911 | +67% | 0 | 0 | — |
case-22 | fail→pass | 23,860 | 26,000 | +9% | 1 | 1 | 0% | 3,815 | 7,099 | +86% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.