From 4f4dde988a1b2f7ec9d2e8acd4e84fc9785ce82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 23 Mar 2026 00:43:40 +0100 Subject: [PATCH] Add Writable --- packages/effect-fc/src/Writable.ts | 116 ++++++----------------------- 1 file changed, 21 insertions(+), 95 deletions(-) diff --git a/packages/effect-fc/src/Writable.ts b/packages/effect-fc/src/Writable.ts index 4f1411f..071ad95 100644 --- a/packages/effect-fc/src/Writable.ts +++ b/packages/effect-fc/src/Writable.ts @@ -1,112 +1,38 @@ -import { Effect, Function, Pipeable, Predicate, Readable } from "effect" -import type { NoInfer } from "effect/Types" +import { Effect, Pipeable, Predicate } from "effect" -/** - * @category type ids - */ -export const TypeId: unique symbol = Symbol.for("@effect-fc/Writable") +export const WritableTypeId: unique symbol = Symbol.for("@effect-fc/Writable/Writable") +export type WritableTypeId = typeof WritableTypeId -/** - * @category type ids - */ -export type TypeId = typeof TypeId - -/** - * Represents a value that can be written to. - * - * Dual to `Readable`, which represents a value that can be read. - * A `Writable` exposes a `set` operation that accepts a value of type - * `A` and returns an `Effect`. - * - * @category models - */ -export interface Writable extends Pipeable.Pipeable { - readonly [TypeId]: TypeId - readonly set: (value: A) => Effect.Effect +export interface Writable extends WritablePrototype { + readonly modify: (f: (a: A) => readonly [B, A]) => Effect.Effect } -/** - * A `ReadWritable` is both `Readable` and `Writable` — it can be both read - * from and written to. - * - * @category models - */ -export interface ReadWritable - extends Readable.Readable, Writable {} -/** - * @category refinements - */ -export const isWritable = (u: unknown): u is Writable => - Predicate.hasProperty(u, TypeId) - -const Proto: Pick, typeof TypeId | "pipe"> = { - [TypeId]: TypeId, - pipe(...args: Array) { - return Pipeable.pipeArguments(this, args as any) - }, +export interface WritablePrototype extends Pipeable.Pipeable { + readonly [WritableTypeId]: WritableTypeId } -/** - * Construct a `Writable` from a `set` function. - * - * @category constructors - */ -export const make = (set: (value: A) => Effect.Effect): Writable => { - const self = Object.create(Proto) as Writable - ;(self as any).set = set - return self -} +export const WritablePrototype: WritablePrototype = Object.freeze({ + [WritableTypeId]: WritableTypeId, + ...Pipeable.Prototype, +} as const) + + +export const isWritable = (u: unknown): u is Writable => Predicate.hasProperty(u, WritableTypeId) /** - * Construct a `ReadWritable` from a `get` effect and a `set` function. - * - * @category constructors + * Construct a `Writable` from a `modify` function. */ -export const makeReadWritable = (options: { - readonly get: Effect.Effect - readonly set: (value: A) => Effect.Effect -}): ReadWritable => { - const self = Object.create(Proto) as ReadWritable - ;(self as any).get = options.get - ;(self as any).set = options.set - ;(self as any)[Readable.TypeId] = Readable.TypeId - return self -} - -/** - * Transform the input of a `Writable` using a pure function. - * The resulting `Writable` accepts values of type `B`, maps them to `A` with - * `f`, and then forwards them to the original `set`. - * - * @category combinators - */ -export const contramap: { - (f: (b: NoInfer) => A): (self: Writable) => Writable - (self: Writable, f: (b: NoInfer) => A): Writable -} = Function.dual(2, (self: Writable, f: (b: B) => A): Writable => - make((b: B) => self.set(f(b))) -) - -/** - * Transform the input of a `Writable` using an effectful function. - * - * @category combinators - */ -export const contramapEffect: { - (f: (b: NoInfer) => Effect.Effect): (self: Writable) => Writable - (self: Writable, f: (b: NoInfer) => Effect.Effect): Writable -} = Function.dual(2, (self: Writable, f: (b: B) => Effect.Effect): Writable => - make((b: B) => Effect.flatMap(f(b), (a) => self.set(a))) -) +export const make = ( + modify: (f: (a: A) => readonly [B, A]) => Effect.Effect, +): Writable => Object.setPrototypeOf({ modify }, WritablePrototype) /** * Unwrap a `Writable` that is wrapped in an `Effect`. - * - * @category constructors */ export const unwrap = ( effect: Effect.Effect, E1, R1>, -): Writable => - make((value: A) => Effect.flatMap(effect, (w) => w.set(value))) +): Writable => make(( + f: (a: A) => readonly [B, A] +) => Effect.flatMap(effect, w => w.modify(f)))