From 404910e74b500788ac4e71ddd303a39234541b04 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 17 Dec 2025 12:57:11 -0600 Subject: [PATCH 1/3] add TraceableValue[F[A, B]] instance for F[_, _]: Bifoldable --- .../src/test/scala/TraceValueTest.scala | 65 +++++++++++++++++++ .../shared/src/main/scala/TraceValue.scala | 12 ++++ 2 files changed, 77 insertions(+) diff --git a/modules/core-tests/shared/src/test/scala/TraceValueTest.scala b/modules/core-tests/shared/src/test/scala/TraceValueTest.scala index 2daeaebf..ee7de8d8 100644 --- a/modules/core-tests/shared/src/test/scala/TraceValueTest.scala +++ b/modules/core-tests/shared/src/test/scala/TraceValueTest.scala @@ -5,6 +5,10 @@ package natchez import cats.Id +import cats.data.* +import cats.laws.discipline.arbitrary.* +import munit.ScalaCheckSuite +import org.scalacheck.Prop object TraceValueTest { // should compile @@ -13,3 +17,64 @@ object TraceValueTest { def traceValueFloat() = Trace.Implicits.noop[Id].put(fields = ("foo", 1.0f)) def traceValueDouble() = Trace.Implicits.noop[Id].put(fields = ("foo", 1.0d)) } + +class TraceableValueSpec extends ScalaCheckSuite { + + test( + "TraceableValue[Either[String, Int]] should be a TraceValue.StringValue for Left and TraceValue.NumberValue for Right" + ) { + Prop.forAll { (input: Either[String, Int]) => + val output = TraceableValue[Either[String, Int]].toTraceValue(input) + + input match { + case Left(l) => assertEquals(output, TraceValue.StringValue(l)) + case Right(r) => assertEquals(output, TraceValue.NumberValue(r)) + } + } + } + + test( + "TraceableValue[Validated[String, Int]] should be a TraceValue.StringValue for Invalid and TraceValue.NumberValue for Valid" + ) { + Prop.forAll { (input: Validated[String, Int]) => + val output = TraceableValue[Validated[String, Int]].toTraceValue(input) + + input match { + case Validated.Invalid(l) => assertEquals(output, TraceValue.StringValue(l)) + case Validated.Valid(r) => assertEquals(output, TraceValue.NumberValue(r)) + } + } + } + + test( + "TraceableValue[Ior[String, Int]] should be a TraceValue.StringValue for Left and TraceValue.NumberValue for Right or Both" + ) { + Prop.forAll { (input: Ior[String, Int]) => + val output = TraceableValue[Ior[String, Int]].toTraceValue(input) + + input match { + case Ior.Left(l) => assertEquals(output, TraceValue.StringValue(l)) + case Ior.Right(r) => assertEquals(output, TraceValue.NumberValue(r)) + case Ior.Both(_, r) => + assertEquals(output, TraceValue.NumberValue(r)) + } + } + } + + test("TraceableValue[(String, Int)] should be a TraceValue.NumberValue") { + Prop.forAll { (input: (String, Int)) => + val output = TraceableValue[(String, Int)].toTraceValue(input) + + assertEquals(output, TraceValue.NumberValue(input._2)) + } + } + + test("TraceableValue[Const[String, *]] should be a TraceValue.StringValue") { + Prop.forAll { (input: Const[String, Int]) => + val output = TraceableValue[Const[String, Int]].toTraceValue(input) + + assertEquals(output, TraceValue.StringValue(input.getConst)) + } + } + +} diff --git a/modules/core/shared/src/main/scala/TraceValue.scala b/modules/core/shared/src/main/scala/TraceValue.scala index c388886c..bb77ccfe 100644 --- a/modules/core/shared/src/main/scala/TraceValue.scala +++ b/modules/core/shared/src/main/scala/TraceValue.scala @@ -4,6 +4,8 @@ package natchez +import cats.* + sealed trait TraceValue extends Product with Serializable { def value: Any } @@ -57,4 +59,14 @@ object TraceableValue { implicit val longToTraceValue: TraceableValue[Long] = TraceValue.NumberValue(_) implicit val doubleToTraceValue: TraceableValue[Double] = TraceValue.NumberValue(_) implicit val floatToTraceValue: TraceableValue[Float] = TraceValue.NumberValue(_) + + implicit def bifoldableTraceableValue[F[_, _]: Bifoldable, A: TraceableValue, B: TraceableValue] + : TraceableValue[F[A, B]] = + Bifoldable[F] + .bifoldRight(_, Eval.later[TraceValue](???))( + (a, _) => Eval.now(TraceableValue[A].toTraceValue(a)), + (b, _) => Eval.now(TraceableValue[B].toTraceValue(b)) + ) + .value + } From 7534a3bca99069d608edc0956df92f2beca79fce Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Fri, 19 Dec 2025 18:42:54 -0600 Subject: [PATCH 2/3] add traceValueIdentity: TraceableValue[TraceValue] --- modules/core/shared/src/main/scala/TraceValue.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/core/shared/src/main/scala/TraceValue.scala b/modules/core/shared/src/main/scala/TraceValue.scala index bb77ccfe..1fdc1134 100644 --- a/modules/core/shared/src/main/scala/TraceValue.scala +++ b/modules/core/shared/src/main/scala/TraceValue.scala @@ -60,6 +60,8 @@ object TraceableValue { implicit val doubleToTraceValue: TraceableValue[Double] = TraceValue.NumberValue(_) implicit val floatToTraceValue: TraceableValue[Float] = TraceValue.NumberValue(_) + implicit val traceValueIdentity: TraceableValue[TraceValue] = identity + implicit def bifoldableTraceableValue[F[_, _]: Bifoldable, A: TraceableValue, B: TraceableValue] : TraceableValue[F[A, B]] = Bifoldable[F] From 72e32d45eca6bb40bfeed34bf11bea4fa1144823 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Mon, 29 Dec 2025 18:50:11 -0600 Subject: [PATCH 3/3] add @FunctionalInterface to TraceableValue to resolve warnings --- modules/core/shared/src/main/scala/TraceValue.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/core/shared/src/main/scala/TraceValue.scala b/modules/core/shared/src/main/scala/TraceValue.scala index 1fdc1134..8d747003 100644 --- a/modules/core/shared/src/main/scala/TraceValue.scala +++ b/modules/core/shared/src/main/scala/TraceValue.scala @@ -43,6 +43,7 @@ object TraceValue { * * @tparam A The type to be converted to `TraceValue` */ +@FunctionalInterface trait TraceableValue[A] { outer => def toTraceValue(a: A): TraceValue