В демо видно, что дочерний
FiberRef переопределяет родительский при join. Но что будет если в FiberRef будет храниться не примитивное значение? Можем получить неожиданное поведение - например как тут потерялось "new A":const defaultSettings = new Settings("default", 0)
let defaultSettingsFiberRef = FiberRef.unsafeMake(defaultSettings)
const runnable = Effect.gen(function*() {
const ref = yield* defaultSettingsFiberRef
yield* Effect.log(ref)
// Settings("default", 0)
yield* Effect.zip(
FiberRef.update(defaultSettingsFiberRef, (x) => x.setA("new A")),
FiberRef.update(defaultSettingsFiberRef, (x) => x.setB(111111)),
{ concurrent: true }
)
const newRef = yield* defaultSettingsFiberRef
yield* Effect.log(newRef)
// Settings("default", 111111)
})
Чтобы решить проблему используется подход как в контролях версий - давайте хранить патчи и накатывать их когда надо. Это знание хранит в себе
Differ:interface Differ<in out Value, in out Patch> {
readonly empty: Patch
diff(oldValue: Value, newValue: Value): Patch
combine(first: Patch, second: Patch): Patch
patch(patch: Patch, oldValue: Value): Value
}
Можно сказать что это
Monoid<Patch>, который умеет находить Patch для Value (diff) и как-то их накатывать (patch). combine — ассоциативная операция (a*(b*c)=(a*b)*c), чтобы при различных порядках Fiber.join результат FiberRef не менялся. Вот наивный пример как сделать
Diff<Settings, ...>А этот более интересный с применением комбинаторов и конструкторов для примитивов - идея в том что кастомная сущность мапится на более элементарную структуру и обратно, но для этой простой структуры известен
Diff. В типе видно, что для рассчетов применяется более примитивный патч.Differ<Settings, readonly [(a: string) => string, (a: number) => number]>
- ZIO Doc — https://zio.dev/reference/state-management/fiberref
- Видео по доке — https://www.youtube.com/watch?v=zpwmsYce8KU