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.get | FiberRef<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) => FiberRefs | Fork для нового файбера |
FiberRefs.joinAs | (FiberRefs, FiberId, FiberRefs) => FiberRefs | Join при завершении |
Утилиты
| Функция | Описание |
|---|---|
FiberRef.delete | Сбросить к начальному значению |
FiberRef.reset | Alias для 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)
// Увеличьте счётчик в родительском файбере
// Создайте дочерний файбер и увеличьте там
// Покажите, что значения изолированы
???
})import { FiberRef, Effect, Fiber } from "effect"
const program = Effect.gen(function* () {
const counter = yield* FiberRef.make(0)
// Увеличиваем в родителе
yield* FiberRef.update(counter, (n) => n + 10)
console.log("Parent after +10:", yield* FiberRef.get(counter)) // 10
// Дочерний файбер
const fiber = yield* Effect.fork(
Effect.gen(function* () {
console.log("Child inherited:", yield* FiberRef.get(counter)) // 10
yield* FiberRef.update(counter, (n) => n + 100)
console.log("Child after +100:", yield* FiberRef.get(counter)) // 110
return yield* FiberRef.get(counter)
})
)
const childResult = yield* Fiber.join(fiber)
console.log("Child returned:", childResult) // 110
console.log("Parent unchanged:", yield* FiberRef.get(counter)) // 10
})
Effect.runFork(program)Упражнение 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
???
})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") // [INFO]
yield* FiberRef.locally(logLevelRef, "debug")(
Effect.gen(function* () {
yield* log("Debug message 1") // [DEBUG]
yield* log("Debug message 2") // [DEBUG]
})
)
yield* log("Back to normal") // [INFO]
})
Effect.runFork(program)
/*
Output:
[INFO] Normal log
[DEBUG] Debug message 1
[DEBUG] Debug message 2
[INFO] Back to normal
*/Упражнение 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))
})
)import { FiberRef, Effect, Fiber } from "effect"
interface RequestContext {
readonly requestId: string
readonly userId: number
readonly startTime: number
}
const requestContextRef = FiberRef.unsafeMake<RequestContext | null>(null)
const withRequest = <A, E, R>(
requestId: string,
userId: number,
effect: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
FiberRef.locally(requestContextRef, {
requestId,
userId,
startTime: Date.now()
})(effect)
const getRequestInfo = (): Effect.Effect<string> =>
Effect.gen(function* () {
const ctx = yield* FiberRef.get(requestContextRef)
if (ctx === null) {
return "No request context"
}
return `Request: ${ctx.requestId}, User: ${ctx.userId}, Started: ${ctx.startTime}`
})
const program = withRequest("req-123", 42,
Effect.gen(function* () {
yield* Effect.log(yield* getRequestInfo())
const fiber = yield* Effect.fork(
Effect.gen(function* () {
yield* Effect.log(yield* getRequestInfo())
})
)
yield* Fiber.join(fiber)
})
)
Effect.runFork(program)
/*
Output:
timestamp=... message="Request: req-123, User: 42, Started: ..."
timestamp=... message="Request: req-123, User: 42, Started: ..."
*/Упражнение 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> =>
???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
}
const generateId = () => Math.random().toString(36).substring(2, 10)
const collectedSpans = FiberRef.unsafeMake<Chunk.Chunk<Span>>(Chunk.empty())
const currentSpan = FiberRef.unsafeMake<Span | null>(null)
const withSpan = <A, E, R>(
name: string,
effect: Effect.Effect<A, E, R>
): Effect.Effect<A, E, R> =>
Effect.gen(function* () {
const parent = yield* FiberRef.get(currentSpan)
const span: Span = {
traceId: parent?.traceId ?? generateId(),
spanId: generateId(),
parentSpanId: parent?.spanId ?? null,
name,
startTime: Date.now()
}
return yield* FiberRef.locally(currentSpan, span)(
Effect.gen(function* () {
try {
return yield* effect
} finally {
const completedSpan: Span = {
...span,
endTime: Date.now()
}
yield* FiberRef.update(collectedSpans, Chunk.append(completedSpan))
}
})
)
})
const getSpans = FiberRef.get(collectedSpans)
// Тест
const program = Effect.gen(function* () {
yield* withSpan("root",
Effect.gen(function* () {
yield* withSpan("child-1",
Effect.sleep("50 millis")
)
yield* Effect.all([
withSpan("child-2a", Effect.sleep("30 millis")),
withSpan("child-2b", Effect.sleep("40 millis"))
], { concurrency: "unbounded" })
})
)
const spans = yield* getSpans
console.log("Collected spans:")
for (const span of spans) {
console.log(` ${span.name}: ${span.spanId} (parent: ${span.parentSpanId ?? "none"})`)
}
})
Effect.runFork(program)Упражнение 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
})
)
})import { FiberRef, FiberRefs, Effect, Fiber } from "effect"
const createSnapshot = (): Effect.Effect<FiberRefs.FiberRefs> =>
Effect.getFiberRefs
const restoreSnapshot = (snapshot: FiberRefs.FiberRefs) =>
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
Effect.gen(function* () {
// Получаем текущие refs для восстановления после
const current = yield* Effect.getFiberRefs
// Устанавливаем снимок
yield* Effect.setFiberRefs(snapshot)
// Выполняем эффект
try {
return yield* effect
} finally {
// Восстанавливаем оригинальные refs
yield* Effect.setFiberRefs(current)
}
})
// Использование
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* () {
// Примечание: FiberRefs восстанавливает состояние
// но для глобальных unsafeMake refs это сложнее
console.log("Inside restored scope")
})
)
})
Effect.runFork(program)Заключение
FiberRef и FiberRefs предоставляют мощные механизмы для работы с fiber-local данными:
FiberRef:
- Изоляция — каждый файбер имеет свою копию
- Наследование — дочерние файберы автоматически получают значения
- Locally — временные изменения в ограниченном scope
- Настраиваемая семантика — кастомное поведение fork/join
FiberRefs:
- Полный снимок состояния всех FiberRef файбера
- Ключевой компонент Runtime системы
- Поддержка патчей для эффективного обновления
- Основа для создания изолированных Runtime окружений
В следующей статье мы изучим Supervisor — механизм супервизии и мониторинга файберов.