AgentSkillsCN

effects

OCaml 5 中的代数效应设计模式。当 Claude 需要:(1) 设计与基于效应的调度器交互的 API;(2) 在效应与异常之间做出抉择;(3) 将库与 Eio 或 affect 集成;(4) 在流式代码中妥善处理挂起与错误场景;(5) 深入理解分层效应设计原则时,此模式便能大显身手。

SKILL.md
--- frontmatter
name: effects
description: "OCaml 5 algebraic effects design patterns. Use when Claude needs to: (1) Design APIs that interact with effect-based schedulers, (2) Decide between effects vs exceptions, (3) Integrate libraries with Eio or affect, (4) Handle suspension vs error cases in streaming code, (5) Understand the layered effect design principle"
license: ISC

OCaml 5 Effects Design

Core Principle

Effects for control flow, exceptions for errors.

ConcernMechanismExample
Suspension (wait for data)Effectsperform Block, perform Yield
Error (EOF, malformed)Exceptionsraise End_of_file, Invalid_argument

Layered Design

Effects should be handled at the source level, not in protocol parsers:

code
Application
    ↓
Protocol parser (Binary.Reader, Cbor, etc.)
    ↓  raises exceptions on EOF/error
bytesrw (effect-agnostic)
    ↓  just calls pull function
Source (Eio flow, affect fd, Unix fd)
    ↓  performs effects for suspension
Effect handler (Eio scheduler, affect runtime)

Why This Matters

  • Parsers stay pure: No effect dependencies, easy to test
  • Sources control blocking: Handler decides wait vs fail vs timeout
  • Composability: Same parser works with any effect system

Effect Libraries

Eio

Effects are internal to the scheduler. User code looks synchronous:

ocaml
(* Reading blocks via internal effects *)
let data = Eio.Flow.read flow buf

affect

Explicit effects for fiber scheduling:

ocaml
type _ Effect.t +=
| Block : 'a block -> 'a Effect.t   (* suspension *)
| Await : await -> unit Effect.t    (* wait on fibers *)
| Yield : unit Effect.t             (* cooperative yield *)

(* Block has callbacks for scheduler integration *)
type 'a block = {
  block : handle -> unit;      (* register blocked fiber *)
  cancel : handle -> bool;     (* handle cancellation *)
  return : handle -> 'a        (* extract result *)
}

bytesrw

Effect-agnostic streaming. The pull function you provide can perform any effects:

ocaml
(* bytesrw just calls your function *)
let reader = Bytesrw.Bytes.Reader.make my_pull_fn

(* If my_pull_fn performs Eio effects, they propagate *)
(* If my_pull_fn performs affect Block, they propagate *)
(* bytesrw doesn't care - it just calls the function *)

Integration Pattern

Wire effect-performing sources to effect-agnostic libraries:

ocaml
(* With Eio *)
let reader = Bytesrw_eio.bytes_reader_of_flow flow in
let r = Binary.Reader.of_reader reader in
parse r  (* Eio effects happen in pull function *)

(* With affect *)
let pull () =
  let buf = Bytes.create 4096 in
  perform (Block { block; cancel; return = fun _ ->
    Slice.make buf ~first:0 ~length:n })
in
let reader = Bytesrw.Bytes.Reader.make pull in
parse (Binary.Reader.of_reader reader)

When EOF Is Reached

Slice.eod from bytesrw means final EOF - no more data will ever come.

  • Not "data not ready" (that's handled by effects in pull function)
  • Not "try again later" (source already waited via effects)
  • Parser should raise exception (EOF is an error condition)

Anti-Patterns

Don't: Define Await effect in protocol parsers

ocaml
(* WRONG - parser shouldn't know about suspension *)
let get_byte t =
  if no_data then perform Await; ...

Do: Let the source handle suspension

ocaml
(* RIGHT - parser just reads, source handles waiting *)
let get_byte t =
  match pull_next_slice t with  (* may perform effects *)
  | Some slice -> ...
  | None -> raise End_of_file   (* true EOF *)

References