From 43b6efabc9a2c53ff9da1735d25d6ca49ed05422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Costa?= Date: Sun, 30 Jul 2023 21:22:24 +0200 Subject: [PATCH] Add withRefs/asRefs --- .../scala/eu/joaocosta/interim/api/Ref.scala | 26 +++++++++++++++++++ .../eu/joaocosta/interim/api/RefSpec.scala | 19 ++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/core/src/main/scala/eu/joaocosta/interim/api/Ref.scala b/core/src/main/scala/eu/joaocosta/interim/api/Ref.scala index e5dfc11..8e87e53 100644 --- a/core/src/main/scala/eu/joaocosta/interim/api/Ref.scala +++ b/core/src/main/scala/eu/joaocosta/interim/api/Ref.scala @@ -1,5 +1,7 @@ package eu.joaocosta.interim.api +import scala.deriving.Mirror + /** A mutable reference to a variable. * * When a function receives a Ref as an argument, it will probably mutate it. @@ -47,8 +49,32 @@ object Ref: block(ref) ref.value + /** Destructures an object into a tuple of Refs that can be used inside the block. + * In the end, a new object is returned with the updated values + * + * Useful to set temporary mutable variables. + */ + def withRefs[T <: Product](initialValue: T)(using mirror: Mirror.ProductOf[T])( + block: Tuple.Map[mirror.MirroredElemTypes, Ref] => Unit + ): T = + val tuple: mirror.MirroredElemTypes = Tuple.fromProductTyped(initialValue) + val refTuple: Tuple.Map[tuple.type, Ref] = tuple.map([T] => (x: T) => Ref(x)) + block(refTuple.asInstanceOf) + type UnRef[T] = T match { case Ref[a] => a } + val updatedTuple: mirror.MirroredElemTypes = + refTuple.map([T] => (x: T) => x.asInstanceOf[Ref[_]].value.asInstanceOf[UnRef[T]]).asInstanceOf + mirror.fromTuple(updatedTuple) + /** Wraps this value into a Ref and passes it to a block, returning the final value of the ref. * * Useful to set temporary mutable variables. */ extension [T](x: T) def asRef(block: Ref[T] => Unit): T = withRef(x)(block) + + /** Destructures this value into multiple Refs and passes it to a block, returning the final value of the ref. + * + * Useful to set temporary mutable variables. + */ + extension [T <: Product](x: T) + def asRefs(using mirror: Mirror.ProductOf[T])(block: Tuple.Map[mirror.MirroredElemTypes, Ref] => Unit): T = + withRefs(x)(block) diff --git a/core/src/test/scala/eu/joaocosta/interim/api/RefSpec.scala b/core/src/test/scala/eu/joaocosta/interim/api/RefSpec.scala index c045a68..64b1c28 100644 --- a/core/src/test/scala/eu/joaocosta/interim/api/RefSpec.scala +++ b/core/src/test/scala/eu/joaocosta/interim/api/RefSpec.scala @@ -38,9 +38,28 @@ class RefSpec extends munit.FunSuite: } assertEquals(result, 2) + // Braces needed due to https://github.com/scalameta/scalafmt/issues/3597 + test("withRefs allows to build a case class from temporary Ref value") { + case class Foo(x: Int, y: String) + val result = Ref.withRefs(Foo(1, "asd")) { (x, y) => + x := 2 + y := "dsa" + } + assertEquals(result, Foo(2, "dsa")) + } + test("asRef allows to use a temporary Ref value"): import Ref.asRef val result = 0.asRef { ref => Ref.modify[Int](ref, _ + 2) } assertEquals(result, 2) + + test("asRefs allows to build a case class from temporary Ref value"): + import Ref.asRefs + case class Foo(x: Int, y: String) + val result = Foo(1, "asd").asRefs { (x, y) => + x := 2 + y := "dsa" + } + assertEquals(result, Foo(2, "dsa"))