modify performs one atomic state transition while returning a separate result. Given a function of type A => (A, B), it computes the replacement state A and exposes B to the surrounding effect. section 15.3.3
For a Ref, the transition reads the current value, applies the function, and attempts to install the new value. If another thread changes the value first, the operation retries from the newly observed state. This keeps the state update and returned result aligned. section 15.3.3
Building a scope state machine
A scope stores Open or Closed state in a Ref. Opening an open scope appends a new child and returns an effect producing that child: state.modify { case Open(finalizer, subs) => Open(finalizer, subs :+ child) -> F.unit(child); case Closed() => Closed() -> next }. The resulting F[F[Scope[F]]] is flattened because modify returns an effect as its additional value. Closing uses the same pattern to mark the scope Closed while returning the effect that runs finalizers. section 15.3.3
final class Ref[F[_], A] private (
underlying: AtomicReference[A],
delay: [A] => (() => A) => F[A] #1
):
def get: F[A] = delay(() => underlying.get)
def set(a: A): F[Unit] = delay(() => underlying.set(a))
def modify[B](f: A => (A, B)): F[B] = delay: () =>
@annotation.tailrec
def loop(): B =
val oldA = underlying.get
val (newA, result) = f(oldA)
if underlying.compareAndSet(oldA, newA) then result else loop()
loop()
object Ref:
def apply[F[_], A](initial: A)(using F: Monad[F]): Ref[F, A] =
new Ref(new AtomicReference[A](initial),
[A] => (th: () => A) => F.unit(()).map(_ => th()))
Inspect the complete Ref implementation, including get, set, the compare-and-set retry loop, and construction from a Monad. section 15.3.3
The same functional shape appears in State: get the current state, set f(s), and yield Unit. Ref.modify adds atomicity for shared effectful state; State.modify expresses a pure state action. section 6.6 section 15.3.3
