local atom = require(script.Parent.atom) type Atom = atom.Atom type ReadonlyAtom = atom.ReadonlyAtom local reference = require(script.Parent.utils.reference) type Derive = ((Atom, (A) -> T) -> ReadonlyAtom) & ((Atom, Atom, (A, B) -> T) -> ReadonlyAtom) & ((Atom, Atom, Atom, (A, B, C) -> T) -> ReadonlyAtom) & ((Atom, Atom, Atom, Atom, (A, B, C, D) -> T) -> ReadonlyAtom) & ((Atom, Atom, Atom, Atom, Atom, (A, B, C, D, E) -> T) -> ReadonlyAtom) & ((...(Atom | ((...any) -> T))) -> ReadonlyAtom) local function derive(...): ReadonlyAtom local atoms: { Atom } = { ... } local combiner: (...unknown) -> Result = table.remove(atoms) :: any local size = #atoms local dependencies: { unknown } = {} for index, atom in atoms do dependencies[index] = atom:get() end local derivedAtom = atom(combiner(table.unpack(dependencies, 1, size))):readonly() local getDerivedAtom = reference.weak(derivedAtom) for index, atom in atoms do local function listener(state: unknown) -- Remove the listener if the derived atom has been garbage collected, -- typically because no subscriptions to it exist. local derivedAtom = getDerivedAtom() if derivedAtom then dependencies[index] = state derivedAtom:setForced(combiner(table.unpack(dependencies, 1, size))) else atom.listeners[listener] = nil end end -- Tie the lifetime of the derived atom to the lifetime of its dependencies -- without creating a strong reference cycle. As long as an atom is in use, -- its derived atoms will be in use as well. derivedAtom.owners[atom] = true atom.listeners[listener] = true end return derivedAtom end return derive :: Derive