Search code examples
stmzio

ZIO STM not mutating


This prints out the unchanged value of 5:

import zio.stm._
import zio.Console
import zio.Runtime.{default => rt}

class TInt(n: TRef[Int]):
  def ++(): USTM[Unit] = n.update(_ + 1)
  override def toString: String = n.toString

object TInt:
  def make(n: Int): USTM[TInt] = TRef.make(n).map(TInt(_))

class Client:
  val stmN: USTM[TInt] = TInt.make(5)
  def increment: USTM[Unit] = stmN.flatMap(_.++())

val c = Client()
rt.unsafeRun(for
  _ <- c.increment.commit
  n <- c.stmN.commit
  _ <- Console.printLine(n)
yield ())

How can I (w/ minimal structural changes) get it to print out the incremented value instead? With some testing, I know that TInt is solid. The issue, I suspect, has to do w/ Client.stmN being a USTM and not reflecting the underlying mutation.


Solution

  • Undressed stmN: STM[TInt] and now it's simply n: Tint and being tracked as a constructor parameter. This requires construction of the Client to be pulled into, effectively, a TRef.map:

    import zio.stm._
    import zio.Console
    import zio.Runtime.{default => rt}
    
    class TInt(n: TRef[Int]):
      def ++(): USTM[Unit] = n.update(_ + 1)
      override def toString: String = n.toString
    
    object TInt:
      def make(n: Int): USTM[TInt] = TRef.make(n).map(TInt(_))
    
    class Client(val n: TInt):
      def increment: USTM[Unit] = n.++()
    
    object Client:
      def apply(): USTM[Client] = TInt.make(5).map(new Client(_))
    
    rt.unsafeRun(for
      c <- Client().commit
      _ <- c.increment.commit
      _ <- Console.printLine(c.n)
    yield ())
    

    The effect of this is that c is now part of the returning value w/in unsafeRun. Seems like Client needed to be wrapped w/ a USTM in order for its mutability to be taken seriously. In general, it is best, I have found, to not spread transactions amongst various classes w/in composition trees. While it does make for a nice OO design, it makes it difficult to get the mutability desired. Instead, try to keep everything on the same class and just use stm.T* as the state.