Effect.ts: Absence as First-Class

State ManagementLesson 51 of 51

32. Ref: Practical Patterns

Common real-world uses of Ref with examples

Pattern 1: Counter/Accumulator

Track a running count or sum:

const processItems = (items: string[]) => Effect.gen(function* () {
  const processed = yield* Ref.make(0);

  for (const item of items) {
    yield* processItem(item);
    yield* Ref.update(processed, n => n + 1);
  }

  const total = yield* Ref.get(processed);
  console.log(`Processed ${total} items`);
});

Use for: Progress tracking, statistics, metrics

Pattern 2: Boolean Flags

Track on/off state:

const modal = Effect.gen(function* () {
  const isOpen = yield* Ref.make(false);

  // Open
  yield* Ref.set(isOpen, true);

  // Toggle
  yield* Ref.update(isOpen, b => !b);

  // Check state
  if (yield* Ref.get(isOpen)) {
    console.log("Modal is open");
  }
});

Use for: Loading states, visibility toggles, feature flags

Pattern 3: Collecting Results

Accumulate values from parallel operations:

const fetchAll = (urls: string[]) => Effect.gen(function* () {
  const results = yield* Ref.make<Response[]>([]);
  const errors = yield* Ref.make<Error[]>([]);

  const fetchOne = (url: string) => Effect.gen(function* () {
    try {
      const response = yield* fetch(url);
      yield* Ref.update(results, arr => [...arr, response]);
    } catch (error) {
      yield* Ref.update(errors, arr => [...arr, error]);
    }
  });

  yield* Effect.all(
    urls.map(fetchOne),
    { concurrency: 5 }
  );

  return {
    results: yield* Ref.get(results),
    errors: yield* Ref.get(errors)
  };
});

Use for: Parallel aggregation, error collection, result batching

Pattern 4: Cache/Memoization

Store computed values:

const cache = Effect.gen(function* () {
  const computed = yield* Ref.make<Map<string, number>>(new Map());

  const getOrCompute = (key: string) => Effect.gen(function* () {
    const cache = yield* Ref.get(computed);

    if (cache.has(key)) {
      return cache.get(key)!; // Cache hit
    }

    // Cache miss - compute it
    const value = expensiveComputation(key);

    yield* Ref.update(computed, m =>
      new Map(m).set(key, value)
    );

    return value;
  });

  return getOrCompute;
});

Use for: Memoization, caching, lookup tables

Pattern 5: State Machine

Track state transitions:

type Status = "idle" | "loading" | "success" | "error";

const stateMachine = Effect.gen(function* () {
  const status = yield* Ref.make<Status>("idle");

  const start = () => Ref.set(status, "loading");
  const succeed = () => Ref.set(status, "success");
  const fail = () => Ref.set(status, "error");
  const reset = () => Ref.set(status, "idle");

  return { status, start, succeed, fail, reset };
});

Use for: Request states, UI states, process stages

Pattern 6: Rate Limiting

Track requests over time:

const rateLimiter = Effect.gen(function* () {
  const requests = yield* Ref.make<Date[]>([]);

  const canMakeRequest = Effect.gen(function* () {
    const now = new Date();
    const oneMinuteAgo = new Date(now.getTime() - 60000);

    // Clean old requests
    yield* Ref.update(requests, arr =>
      arr.filter(date => date > oneMinuteAgo)
    );

    const recent = yield* Ref.get(requests);

    if (recent.length >= 10) {
      return false; // Rate limited!
    }

    // Add this request
    yield* Ref.update(requests, arr => [...arr, now]);
    return true;
  });

  return canMakeRequest;
});

Use for: Rate limiting, throttling, quota tracking

Pattern 7: Shared Configuration

Pass mutable config through a program:

const app = Effect.gen(function* () {
  const config = yield* Ref.make({
    theme: "light",
    language: "en",
    debug: false
  });

  // Update config anywhere
  yield* Ref.update(config, c => ({ ...c, theme: "dark" }));

  // Read config anywhere
  const currentConfig = yield* Ref.get(config);

  if (currentConfig.debug) {
    console.log("Debug mode enabled");
  }
});

Use for: App config, user preferences, feature toggles

Anti-Pattern: Don't Overuse

Bad: Using Ref for everything

const bad = Effect.gen(function* () {
  const a = yield* Ref.make(1);
  const b = yield* Ref.make(2);
  const c = yield* Ref.make(3);
  // Too much mutable state!
});

Good: Use immutable values when possible

const good = Effect.gen(function* () {
  const a = 1;
  const b = 2;
  const c = 3;
  // Simple values, no mutation needed
});

Rule of thumb: Only use Ref when you actually need mutation.

Quick Reference

| Pattern | Use Case | Example | |---------|----------|---------| | Counter | Track quantity | Ref.update(n => n + 1) | | Flag | Boolean state | Ref.set(true) | | Accumulator | Collect values | Ref.update(arr => [...arr, x]) | | Cache | Store lookups | Ref.update(map => map.set(k, v)) | | State Machine | Track status | Ref.set("loading") |


Real-world patterns for everyday Ref usage


Part 51 of 51 in the Effect.ts Absence Modeling series