Effect Курс FiberRef и FiberRefs

FiberRef и FiberRefs

Fiber-Local Storage.

Введение в FiberRef

FiberRef<A> — это переменная, значение которой привязано к конкретному файберу. Каждый файбер может иметь своё собственное значение для одного и того же FiberRef.

Зачем нужен FiberRef?

┌─────────────────────────────────────────────────────────────────┐
│                    СЛУЧАИ ИСПОЛЬЗОВАНИЯ                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Request Context                                             │
│     └── Correlation ID, User ID, Trace ID                       │
│                                                                 │
│  2. Конфигурация на уровне файбера                              │
│     └── Timeout settings, Retry policies                        │
│                                                                 │
│  3. Логирование                                                 │
│     └── Log level, Log annotations                              │
│                                                                 │
│  4. Транзакционный контекст                                     │
│     └── Transaction ID, Isolation level                         │
│                                                                 │
│  5. Feature flags                                               │
│     └── A/B тестирование на уровне запроса                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Сравнение с альтернативами

┌─────────────────────────────────────────────────────────────────┐
│                    FiberRef vs Альтернативы                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Глобальная переменная                                          │
│  ├── Нет изоляции между файберами                               │
│  └── Race conditions                                            │
│                                                                 │
│  Ref<A>                                                         │
│  ├── Shared между всеми файберами                               │
│  └── Требует явной передачи                                     │
│                                                                 │
│  Context/Service                                                │
│  ├── Статический в рамках Effect                                │
│  └── Не меняется динамически                                    │
│                                                                 │
│  FiberRef<A>                                                    │
│  ├── Изоляция по файберам ✓                                     │
│  ├── Автоматическое наследование ✓                              │
│  ├── Динамические изменения ✓                                   │
│  └── Функциональная семантика ✓                                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Концептуальная модель

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  FiberRef<A> = "Ключ"                                           │
│                                                                 │
│  Fiber #0 (root)         Fiber #1 (child)      Fiber #2 (child) │
│  ┌─────────────────┐     ┌─────────────────┐   ┌─────────────┐  │
│  │ FiberRef → "A"  │────►│ FiberRef → "A"  │   │ FiberRef →  │  │
│  │ (initial)       │fork │ (inherited)     │   │    "B"      │  │
│  └─────────────────┘     └─────────────────┘   │ (modified)  │  │
│                                                └─────────────┘  │
│                                                                 │
│  Каждый файбер имеет свою копию значения                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Создание FiberRef

FiberRef.make — Базовое создание


// Создание FiberRef с начальным значением
const myRef: Effect.Effect<FiberRef.FiberRef<number>> = 
  FiberRef.make(0)

// Использование
const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make(0)
  const value = yield* FiberRef.get(ref)
  console.log(value) // 0
})

Effect.runFork(program)

FiberRef.make с опциями


const advancedRef = FiberRef.make(0, {
  // Как объединять значения при fork
  fork: (parent) => parent,  // Дочерний файбер наследует значение
  
  // Как объединять при join файберов
  join: (parent, child) => parent + child
})

const program = Effect.gen(function* () {
  const ref = yield* advancedRef
  
  yield* FiberRef.set(ref, 10)
  
  // Fork дочерний файбер
  const fiber = yield* Effect.fork(
    Effect.gen(function* () {
      yield* FiberRef.update(ref, (n) => n + 5)
      return yield* FiberRef.get(ref)
    })
  )
  
  // При join значения объединяются
  const childResult = yield* Fiber.join(fiber)
  const parentValue = yield* FiberRef.get(ref)
  
  console.log("Child saw:", childResult)   // 15 (10 + 5)
  console.log("Parent has:", parentValue)  // 25 (10 + 15, через join)
})

FiberRef.unsafeMake — Синхронное создание


// Создание без Effect (для глобальных ref)
const globalRef = FiberRef.unsafeMake(42)

// Можно использовать сразу
const program = Effect.gen(function* () {
  const value = yield* FiberRef.get(globalRef)
  console.log(value) // 42
})

Операции чтения и записи

FiberRef.get — Чтение значения


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make("initial")
  
  const value = yield* FiberRef.get(ref)
  console.log(value) // "initial"
})

FiberRef.set — Установка значения


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make(0)
  
  yield* FiberRef.set(ref, 42)
  
  const value = yield* FiberRef.get(ref)
  console.log(value) // 42
})

FiberRef.update — Обновление через функцию


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make(0)
  
  yield* FiberRef.update(ref, (n) => n + 10)
  yield* FiberRef.update(ref, (n) => n * 2)
  
  const value = yield* FiberRef.get(ref)
  console.log(value) // 20
})

FiberRef.modify — Чтение и обновление одновременно


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make(0)
  
  // modify возвращает старое значение и устанавливает новое
  const oldValue = yield* FiberRef.modify(ref, (current) => [
    current,        // Что вернуть
    current + 10    // Новое значение
  ])
  
  console.log("Old:", oldValue)              // 0
  console.log("New:", yield* FiberRef.get(ref)) // 10
})

FiberRef.getAndSet — Атомарная замена


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make("first")
  
  const previous = yield* FiberRef.getAndSet(ref, "second")
  
  console.log("Previous:", previous)              // "first"
  console.log("Current:", yield* FiberRef.get(ref)) // "second"
})

FiberRef.getAndUpdate — Get + Update атомарно


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make(10)
  
  const before = yield* FiberRef.getAndUpdate(ref, (n) => n * 2)
  
  console.log("Before:", before)                  // 10
  console.log("After:", yield* FiberRef.get(ref)) // 20
})

Наследование значений

Когда файбер форкается, дочерний файбер наследует значения FiberRef от родителя.

Базовое наследование


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make("parent-value")
  
  // Дочерний файбер наследует значение
  const fiber = yield* Effect.fork(
    Effect.gen(function* () {
      const inherited = yield* FiberRef.get(ref)
      console.log("Child inherited:", inherited)
      return inherited
    })
  )
  
  const result = yield* Fiber.join(fiber)
  console.log("Child returned:", result)
})

Effect.runFork(program)
/*
Output:
Child inherited: parent-value
Child returned: parent-value
*/

Изоляция изменений

Изменения в дочернем файбере НЕ влияют на родителя (по умолчанию):


const program = Effect.gen(function* () {
  const ref = yield* FiberRef.make(0)
  yield* FiberRef.set(ref, 100)
  
  // Форкаем и модифицируем в дочернем
  const fiber = yield* Effect.fork(
    Effect.gen(function* () {
      yield* FiberRef.set(ref, 999)
      return yield* FiberRef.get(ref)
    })
  )
  
  // Дочерний видит своё значение
  const childValue = yield* Fiber.join(fiber)
  console.log("Child value:", childValue) // 999
  
  // Родитель не затронут
  const parentValue = yield* FiberRef.get(ref)
  console.log("Parent value:", parentValue) // 100
})

Effect.runFork(program)

Диаграмма наследования

Parent Fiber                          Child Fiber
┌────────────────────┐                ┌────────────────────┐
│ FiberRef = 100     │                │                    │
│                    │                │                    │
│    fork ──────────────────────────► │ FiberRef = 100     │
│                    │   (copy)       │ (inherited)        │
│                    │                │                    │
│                    │                │ set(999)           │
│                    │                │ FiberRef = 999     │
│                    │                │                    │
│ FiberRef = 100     │ ◄─── join ──── │ return 999         │
│ (unchanged!)       │                │                    │
└────────────────────┘                └────────────────────┘

Fork и Join семантика

FiberRef позволяет настраивать поведение при fork и join.

Fork семантика


// FiberRef с кастомной fork семантикой
const makeCounterRef = FiberRef.make(0, {
  // При fork дочерний получает parent + 1
  fork: (parent) => parent + 1
})

const program = Effect.gen(function* () {
  const ref = yield* makeCounterRef
  
  console.log("Root fiber:", yield* FiberRef.get(ref)) // 0
  
  const fiber1 = yield* Effect.fork(
    Effect.gen(function* () {
      const value = yield* FiberRef.get(ref)
      console.log("Child 1:", value) // 1
      
      // Внук
      const fiber = yield* Effect.fork(
        Effect.gen(function* () {
          console.log("Grandchild:", yield* FiberRef.get(ref)) // 2
        })
      )
      yield* Fiber.join(fiber)
    })
  )
  
  yield* Fiber.join(fiber1)
})

Effect.runFork(program)
/*
Output:
Root fiber: 0
Child 1: 1
Grandchild: 2
*/

Join семантика


// FiberRef, который суммирует значения при join
const sumRef = FiberRef.make(0, {
  fork: (parent) => parent,
  join: (parent, child) => parent + child
})

const program = Effect.gen(function* () {
  const ref = yield* sumRef
  yield* FiberRef.set(ref, 10)
  
  const fiber = yield* Effect.fork(
    Effect.gen(function* () {
      yield* FiberRef.set(ref, 5)
      return "done"
    })
  )
  
  yield* Fiber.join(fiber)
  
  // Parent теперь имеет 10 + 5 = 15
  const value = yield* FiberRef.get(ref)
  console.log("After join:", value) // 15
})

Effect.runFork(program)

Пример: Accumulating logs


// FiberRef для накопления логов с объединением при join
const globalLogsRef = FiberRef.unsafeMake<Chunk.Chunk<string>>(Chunk.empty())

const logToGlobal = (message: string) =>
  FiberRef.update(globalLogsRef, Chunk.append(message))

const program = Effect.gen(function* () {
  yield* logToGlobal("Main: start")
  
  const fiber1 = yield* Effect.fork(
    Effect.gen(function* () {
      yield* logToGlobal("Child 1: working")
      yield* Effect.sleep("50 millis")
      yield* logToGlobal("Child 1: done")
    })
  )
  
  const fiber2 = yield* Effect.fork(
    Effect.gen(function* () {
      yield* logToGlobal("Child 2: working")
      yield* Effect.sleep("30 millis")
      yield* logToGlobal("Child 2: done")
    })
  )
  
  yield* Fiber.join(fiber1)
  yield* Fiber.join(fiber2)
  
  yield* logToGlobal("Main: end")
  
  const logs = yield* FiberRef.get(globalLogsRef)
  console.log("Collected logs:", Chunk.toArray(logs))
})

Effect.runFork(program)

Встроенные FiberRef

Effect использует FiberRef для многих внутренних механизмов.

Current Log Level


const program = Effect.gen(function* () {
  // Получить текущий уровень логирования
  const currentLevel = yield* FiberRef.get(FiberRef.currentLogLevel)
  console.log("Current log level:", currentLevel)
  
  // Изменить локально
  yield* FiberRef.locally(FiberRef.currentLogLevel, LogLevel.Debug)(
    Effect.gen(function* () {
      yield* Effect.log("This is a debug message")
    })
  )
})

Effect.runFork(program)

Current Log Annotations


const program = Effect.gen(function* () {
  // Добавить аннотации к логам
  yield* Effect.annotateLogs("requestId", "req-123")(
    Effect.gen(function* () {
      yield* Effect.log("Processing request")
      // Лог будет содержать requestId=req-123
    })
  )
})

Effect.runFork(program)

Current Span (для трейсинга)


const program = Effect.gen(function* () {
  yield* Effect.withSpan("my-operation")(
    Effect.gen(function* () {
      yield* Effect.log("Inside span")
      // Текущий span доступен через FiberRef
    })
  )
})

Список встроенных FiberRef

┌─────────────────────────────────────────────────────────────────┐
│                    ВСТРОЕННЫЕ FIBERREF                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  FiberRef.currentLogLevel                                       │
│  └── LogLevel для текущего файбера                              │
│                                                                 │
│  FiberRef.currentLogSpan                                        │
│  └── Текущий log span                                           │
│                                                                 │
│  FiberRef.currentLogAnnotations                                 │
│  └── HashMap с аннотациями для логов                            │
│                                                                 │
│  FiberRef.currentSupervisor                                     │
│  └── Supervisor для текущего файбера                            │
│                                                                 │
│  FiberRef.currentScheduler                                      │
│  └── Scheduler для выполнения эффектов                          │
│                                                                 │
│  FiberRef.interruptedCause                                      │
│  └── Причина прерывания файбера                                 │
│                                                                 │
│  FiberRef.currentTracerSpan                                     │
│  └── Текущий span для distributed tracing                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

FiberRefs — Коллекция FiberRef

FiberRefs — это иммутабельная структура данных, представляющая полный снимок (snapshot) всех значений FiberRef для конкретного файбера. Это ключевой компонент в архитектуре Runtime системы Effect.

Концептуальная модель FiberRefs

┌─────────────────────────────────────────────────────────────────┐
│                        FiberRefs                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  FiberRefs = Map<FiberRef<any>, any>                            │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  FiberRef<LogLevel>    → LogLevel.Info                  │    │
│  │  FiberRef<string>      → "correlation-id-123"           │    │
│  │  FiberRef<number>      → 42                             │    │
│  │  FiberRef<Supervisor>  → Supervisor.track               │    │
│  │  ...                                                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  Иммутабельный snapshot состояния всех FiberRef                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Роль FiberRefs в Runtime

┌─────────────────────────────────────────────────────────────────┐
│                        Runtime<R>                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    Context<R>                           │    │
│  │         (сервисы и зависимости)                         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    FiberRefs                            │    │
│  │  (все FiberRef значения для файбера)                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                   RuntimeFlags                          │    │
│  │  (флаги: interruption, supervisor, и т.д.)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Создание FiberRefs


// Пустая коллекция
const empty = FiberRefs.empty()

// Создание с начальными значениями
const withValues = FiberRefs.unsafeMake(
  new Map([
    [FiberRef.currentLogLevel, { value: LogLevel.Debug }]
  ])
)

Получение значений из FiberRefs


const program = Effect.gen(function* () {
  // Получить текущие FiberRefs из Runtime
  const fiberRefs = yield* Effect.getFiberRefs
  
  // Извлечь конкретное значение
  const logLevel = FiberRefs.getOrDefault(
    fiberRefs, 
    FiberRef.currentLogLevel
  )
  
  console.log("Current log level:", logLevel)
})

Effect.runFork(program)

Обновление FiberRefs


// Исходные FiberRefs
const original = FiberRefs.empty()

// Обновление значения (возвращает новый FiberRefs)
const updated = FiberRefs.updateAs(original, {
  fiberId: FiberId.none,
  fiberRef: FiberRef.currentLogLevel,
  value: LogLevel.Debug
})

// Или через updatedAs для более сложных случаев
const updatedWithPatch = FiberRefs.forkAs(original, FiberId.none)

FiberRefs при Fork

При fork файбера создаётся новый FiberRefs на основе родительского:


const program = Effect.gen(function* () {
  const parentRefs = yield* Effect.getFiberRefs
  
  const fiber = yield* Effect.fork(
    Effect.gen(function* () {
      const childRefs = yield* Effect.getFiberRefs
      // childRefs - копия parentRefs с модификациями 
      // согласно fork семантике каждого FiberRef
      return childRefs
    })
  )
  
  const childRefs = yield* Fiber.join(fiber)
  
  // parentRefs и childRefs - разные объекты
  // но могут иметь одинаковые или разные значения
})

FiberRefsPatch — Патчи для FiberRefs

FiberRefsPatch представляет разницу между двумя состояниями FiberRefs. Используется для эффективного обновления состояния при join.

┌─────────────────────────────────────────────────────────────────┐
│                     FiberRefsPatch                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  FiberRefsPatch = Diff между FiberRefs                          │
│                                                                 │
│  Parent FiberRefs          Child FiberRefs                      │
│  ┌─────────────────┐       ┌─────────────────┐                  │
│  │ logLevel: Info  │       │ logLevel: Debug │                  │
│  │ timeout: 30s    │       │ timeout: 30s    │                  │
│  │ userId: null    │       │ userId: 42      │                  │
│  └─────────────────┘       └─────────────────┘                  │
│          │                          │                           │
│          └──────────┬───────────────┘                           │
│                     │                                           │
│                     ▼                                           │
│           ┌─────────────────┐                                   │
│           │ FiberRefsPatch  │                                   │
│           │ logLevel: Debug │                                   │
│           │ userId: 42      │                                   │
│           └─────────────────┘                                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

// Создание патча
const patch = FiberRefsPatch.diff(
  parentFiberRefs,
  childFiberRefs
)

// Применение патча
const updatedRefs = FiberRefsPatch.patch(
  patch,
  FiberId.none,
  originalRefs
)

Работа с FiberRefs в Runtime


// Создание кастомного Runtime с модифицированными FiberRefs
const customRuntime = Runtime.make({
  context: Context.empty(),
  fiberRefs: FiberRefs.unsafeMake(
    new Map([
      [FiberRef.currentLogLevel, { value: LogLevel.Debug }]
    ])
  ),
  runtimeFlags: RuntimeFlags.none
})

// Запуск эффекта с кастомным Runtime
Runtime.runPromise(customRuntime)(
  Effect.gen(function* () {
    const level = yield* FiberRef.get(FiberRef.currentLogLevel)
    console.log("Log level:", level) // Debug
  })
)

Практический пример: Создание изолированного Runtime

  Runtime, 
  FiberRefs, 
  FiberRef, 
  Effect, 
  Context,
  Layer,
  LogLevel
} from "effect"

// Сервис для логирования
interface Logger {
  readonly log: (message: string) => Effect.Effect<void>
}

const Logger = Context.GenericTag<Logger>("Logger")

// Создание Runtime с кастомными FiberRefs
const createIsolatedRuntime = <R>(
  context: Context.Context<R>,
  options: {
    readonly logLevel?: LogLevel.LogLevel
    readonly timeout?: Duration.DurationInput
  } = {}
) => {
  // Базовые FiberRefs
  let fiberRefs = FiberRefs.empty()
  
  // Применяем опции
  if (options.logLevel) {
    fiberRefs = FiberRefs.updateAs(fiberRefs, {
      fiberId: FiberId.none,
      fiberRef: FiberRef.currentLogLevel,
      value: options.logLevel
    })
  }
  
  return Runtime.make({
    context,
    fiberRefs,
    runtimeFlags: RuntimeFlags.none
  })
}

// Использование
const program = Effect.gen(function* () {
  const level = yield* FiberRef.get(FiberRef.currentLogLevel)
  yield* Effect.log(`Running with log level: ${level}`)
})

const runtime = createIsolatedRuntime(Context.empty(), {
  logLevel: LogLevel.Debug
})

Runtime.runFork(runtime)(program)

FiberRefs в ManagedRuntime


// Layer с кастомными FiberRefs
const CustomFiberRefsLayer = Layer.effectDiscard(
  FiberRef.locally(FiberRef.currentLogLevel, LogLevel.Debug)(
    Effect.void
  )
)

// ManagedRuntime автоматически управляет FiberRefs
const managedRuntime = ManagedRuntime.make(CustomFiberRefsLayer)

const program = Effect.gen(function* () {
  const level = yield* FiberRef.get(FiberRef.currentLogLevel)
  yield* Effect.log(`Level: ${level}`)
})

// FiberRefs наследуются в runtime
managedRuntime.runPromise(program)

Сериализация FiberRefs

FiberRefs можно использовать для передачи контекста между процессами или сервисами:


// Кастомный FiberRef с сериализуемым значением
interface TraceContext {
  readonly traceId: string
  readonly spanId: string
  readonly baggage: Record<string, string>
}

const traceContextRef = FiberRef.unsafeMake<TraceContext | null>(null)

// Извлечение контекста для передачи
const extractContext = Effect.gen(function* () {
  const fiberRefs = yield* Effect.getFiberRefs
  const traceContext = FiberRefs.getOrDefault(fiberRefs, traceContextRef)
  
  return {
    traceContext,
    // Другие сериализуемые значения...
  }
})

// Инъекция контекста из внешнего источника
const injectContext = (serialized: { traceContext: TraceContext | null }) =>
  <A, E, R>(effect: Effect.Effect<A, E, R>) =>
    FiberRef.locally(traceContextRef, serialized.traceContext)(effect)

// Использование для distributed tracing
const handleIncomingRequest = (headers: Record<string, string>) =>
  Effect.gen(function* () {
    // Парсинг trace context из заголовков
    const traceContext: TraceContext = {
      traceId: headers["x-trace-id"] ?? generateId(),
      spanId: generateId(),
      baggage: {}
    }
    
    // Выполнение с контекстом
    yield* injectContext({ traceContext })(
      processRequest
    )
  })

API Reference

Создание FiberRef

ФункцияСигнатураОписание
FiberRef.make(initial, options?) => Effect<FiberRef<A>>Создать FiberRef
FiberRef.unsafeMake(initial, options?) => FiberRef<A>Синхронное создание

Чтение и запись

ФункцияСигнатураОписание
FiberRef.getFiberRef<A> => Effect<A>Получить значение
FiberRef.set(FiberRef<A>, A) => Effect<void>Установить значение
FiberRef.update(FiberRef<A>, f) => Effect<void>Обновить через функцию
FiberRef.modify(FiberRef<A>, f) => Effect<B>Модифицировать и вернуть
FiberRef.getAndSet(FiberRef<A>, A) => Effect<A>Получить и заменить
FiberRef.getAndUpdate(FiberRef<A>, f) => Effect<A>Получить и обновить

FiberRefs API

ФункцияСигнатураОписание
FiberRefs.empty() => FiberRefsПустая коллекция
FiberRefs.get(FiberRefs, FiberRef<A>) => Option<A>Получить значение
FiberRefs.getOrDefault(FiberRefs, FiberRef<A>) => AПолучить или default
FiberRefs.updateAs(FiberRefs, options) => FiberRefsОбновить значение
FiberRefs.forkAs(FiberRefs, FiberId) => FiberRefsFork для нового файбера
FiberRefs.joinAs(FiberRefs, FiberId, FiberRefs) => FiberRefsJoin при завершении

Утилиты

ФункцияОписание
FiberRef.deleteСбросить к начальному значению
FiberRef.resetAlias для delete
Effect.getFiberRefsПолучить FiberRefs из текущего контекста
Effect.setFiberRefsУстановить FiberRefs

Примеры

Пример 1: Request Tracing с FiberRefs


// Trace context
interface TraceContext {
  readonly traceId: string
  readonly spanId: string
  readonly parentSpanId: string | null
}

const traceContextRef = FiberRef.unsafeMake<TraceContext | null>(null)

const generateId = () => Math.random().toString(36).substring(2, 10)

const withTracing = <A, E, R>(
  name: string,
  effect: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  Effect.gen(function* () {
    const parent = yield* FiberRef.get(traceContextRef)
    
    const newContext: TraceContext = {
      traceId: parent?.traceId ?? generateId(),
      spanId: generateId(),
      parentSpanId: parent?.spanId ?? null
    }
    
    return yield* FiberRef.locally(traceContextRef, newContext)(
      Effect.gen(function* () {
        yield* Effect.log(`[${newContext.spanId}] Starting: ${name}`)
        const result = yield* effect
        yield* Effect.log(`[${newContext.spanId}] Completed: ${name}`)
        return result
      })
    )
  })

const program = withTracing("main",
  Effect.gen(function* () {
    yield* withTracing("fetch-user",
      Effect.sleep("100 millis")
    )
    
    yield* Effect.all([
      withTracing("fetch-orders", Effect.sleep("50 millis")),
      withTracing("fetch-products", Effect.sleep("75 millis"))
    ], { concurrency: "unbounded" })
  })
)

Effect.runFork(program)

Пример 2: Feature Flags


type FeatureFlags = HashMap.HashMap<string, boolean>

const featureFlagsRef = FiberRef.unsafeMake<FeatureFlags>(
  HashMap.empty()
)

const isFeatureEnabled = (feature: string) =>
  Effect.gen(function* () {
    const flags = yield* FiberRef.get(featureFlagsRef)
    return HashMap.get(flags, feature).pipe(
      Option.getOrElse(() => false)
    )
  })

const withFeature = <A, E, R>(
  feature: string,
  enabled: boolean,
  effect: Effect.Effect<A, E, R>
) =>
  FiberRef.locallyWith(
    featureFlagsRef,
    HashMap.set(feature, enabled)
  )(effect)

const program = Effect.gen(function* () {
  // По умолчанию выключено
  const v1 = yield* isFeatureEnabled("dark-mode")
  console.log("Dark mode (default):", v1) // false
  
  // Включаем для конкретного контекста
  yield* withFeature("dark-mode", true,
    Effect.gen(function* () {
      const v2 = yield* isFeatureEnabled("dark-mode")
      console.log("Dark mode (enabled):", v2) // true
    })
  )
  
  // Снова выключено
  const v3 = yield* isFeatureEnabled("dark-mode")
  console.log("Dark mode (after):", v3) // false
})

Effect.runFork(program)

Пример 3: Timeout Configuration


const defaultTimeoutRef = FiberRef.unsafeMake<Duration.Duration>(
  Duration.seconds(30)
)

const withTimeout = <A, E, R>(
  timeout: Duration.DurationInput,
  effect: Effect.Effect<A, E, R>
) =>
  FiberRef.locally(defaultTimeoutRef, Duration.decode(timeout))(effect)

const httpRequest = (url: string) =>
  Effect.gen(function* () {
    const timeout = yield* FiberRef.get(defaultTimeoutRef)
    yield* Effect.log(`Requesting ${url} with timeout ${Duration.toMillis(timeout)}ms`)
    
    return yield* Effect.sleep("100 millis").pipe(
      Effect.as({ status: 200, body: "OK" }),
      Effect.timeout(timeout)
    )
  })

const program = Effect.gen(function* () {
  // Использует default timeout (30s)
  yield* httpRequest("/api/users")
  
  // Использует custom timeout
  yield* withTimeout("5 seconds",
    Effect.gen(function* () {
      yield* httpRequest("/api/slow-endpoint")
      yield* httpRequest("/api/another-endpoint")
    })
  )
})

Effect.runFork(program)

Упражнения

Упражнение

Упражнение 1: Counter FiberRef

Легко

Создайте FiberRef-счётчик и продемонстрируйте изоляцию между файберами.

import { FiberRef, Effect, Fiber } from "effect"

const program = Effect.gen(function* () {
  const counter = yield* FiberRef.make(0)
  
  // Увеличьте счётчик в родительском файбере
  // Создайте дочерний файбер и увеличьте там
  // Покажите, что значения изолированы
  ???
})
Упражнение

Упражнение 2: Locally scope

Легко

Используйте locally для временного изменения значения.

import { FiberRef, Effect } from "effect"

const logLevelRef = FiberRef.unsafeMake<"info" | "debug" | "error">("info")

const log = (message: string) =>
  Effect.gen(function* () {
    const level = yield* FiberRef.get(logLevelRef)
    console.log(`[${level.toUpperCase()}] ${message}`)
  })

const program = Effect.gen(function* () {
  yield* log("Normal log")
  
  // Временно включите debug уровень
  // Внутри должен быть DEBUG
  // После должен вернуться INFO
  ???
})
Упражнение

Упражнение 3: Request Context с наследованием

Средне

Реализуйте систему контекста запроса, которая автоматически наследуется в дочерних файберах.

import { FiberRef, Effect, Fiber } from "effect"

interface RequestContext {
  readonly requestId: string
  readonly userId: number
  readonly startTime: number
}

const requestContextRef: FiberRef.FiberRef<RequestContext | null> = ???

const withRequest = <A, E, R>(
  requestId: string,
  userId: number,
  effect: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  ???

const getRequestInfo = (): Effect.Effect<string> =>
  ???

// Тест: вложенные файберы должны видеть контекст
const program = withRequest("req-123", 42,
  Effect.gen(function* () {
    yield* Effect.log(yield* getRequestInfo())
    
    // Дочерний файбер тоже должен видеть контекст
    yield* Effect.fork(
      Effect.gen(function* () {
        yield* Effect.log(yield* getRequestInfo())
      })
    ).pipe(Effect.flatMap(Fiber.join))
  })
)
Упражнение

Упражнение 4: Distributed Tracing с FiberRef

Сложно

Реализуйте распределённый трейсинг с автоматической генерацией span ID.

import { FiberRef, Effect, Fiber, Chunk } from "effect"

interface Span {
  readonly traceId: string
  readonly spanId: string
  readonly parentSpanId: string | null
  readonly name: string
  readonly startTime: number
  readonly endTime?: number
}

// Реализуйте:
// 1. FiberRef для текущего span
// 2. Коллектор завершённых spans
// 3. withSpan функцию, создающую новый span
// 4. Автоматическое наследование traceId

const spans: FiberRef.FiberRef<Chunk.Chunk<Span>> = ???
const currentSpan: FiberRef.FiberRef<Span | null> = ???

const withSpan = <A, E, R>(
  name: string,
  effect: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
  ???
Упражнение

Упражнение 5: Реализация FiberRefs Snapshot

Сложно

Реализуйте механизм создания снимков и восстановления FiberRefs.

import { FiberRef, FiberRefs, Effect } from "effect"

// Реализуйте функции для работы со снимками
const createSnapshot = (): Effect.Effect<FiberRefs.FiberRefs> =>
  ???

const restoreSnapshot = (snapshot: FiberRefs.FiberRefs) =>
  <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
    ???

// Использование
const program = Effect.gen(function* () {
  const myRef = FiberRef.unsafeMake(0)
  yield* FiberRef.set(myRef, 100)
  
  // Создаём снимок
  const snapshot = yield* createSnapshot()
  
  // Меняем значение
  yield* FiberRef.set(myRef, 999)
  console.log("After change:", yield* FiberRef.get(myRef)) // 999
  
  // Восстанавливаем снимок
  yield* restoreSnapshot(snapshot)(
    Effect.gen(function* () {
      console.log("Restored:", yield* FiberRef.get(myRef)) // 100
    })
  )
})

Заключение

FiberRef и FiberRefs предоставляют мощные механизмы для работы с fiber-local данными:

FiberRef:

  • Изоляция — каждый файбер имеет свою копию
  • Наследование — дочерние файберы автоматически получают значения
  • Locally — временные изменения в ограниченном scope
  • Настраиваемая семантика — кастомное поведение fork/join

FiberRefs:

  • Полный снимок состояния всех FiberRef файбера
  • Ключевой компонент Runtime системы
  • Поддержка патчей для эффективного обновления
  • Основа для создания изолированных Runtime окружений

В следующей статье мы изучим Supervisor — механизм супервизии и мониторинга файберов.