I’ve been playing with tuple traversal in Scala 3 and I’ve been increasingly wondering whether I’m doing it right…
The concrete scenario is kind of a minimalist codec framework for the Java Preferences API. I have something like this:
trait PrefsCodec[F[_], A]: def get(using prefs: Preferences): F[Option[A]]
…and, given a tuple of codecs, I want to extract a corresponding tuple of options from the prefs that I can eventually
#mapN() into a final result case class.
For the purpose of this discussion, this reduced example should hopefully suffice:
import cats.* import cats.syntax.all.* case class Box[F[_] : Applicative, A](item: A): def unbox: F[A] = item.pure val boxes = (Box[Option, Int](42), Box[Option, String]("foo"))
Now I’d like to traverse the tuple and invoke
#unbox on each element, resulting in
Obviously I cannot use the rather restricted
#map() over tuples. For similar reasons, I cannot implement a simple recursive traversal function, as I’d have to come up with a bogus fallback for tuple elements that are not a
Box[Option, *]. So I opted for building a “transformer” instance at compile time, inspired by this CSV example.
trait Unboxer[F[_], A, B]: def unbox(a: A): F[B] given [F[_] : Applicative]: Unboxer[F, EmptyTuple, EmptyTuple] with def unbox(empty: EmptyTuple): F[EmptyTuple] = empty.pure given [F[_] : Applicative, T, A <: Tuple, B <: Tuple](using Unboxer[F, A, B]): Unboxer[F, Box[F, T] *: A, T *: B] with def unbox(boxes: Box[F, T] *: A): F[T *: B] = (boxes.head.unbox, summon[Unboxer[F, A, B]].unbox(boxes.tail)).mapN(_ *: _) extension [F[_], A, B](boxes: A)(using Unboxer[F, A, B]) def unbox: F[B] = summon[Unboxer[F, A, B]].unbox(boxes) println(boxes.unbox) // Some((42,foo))
(EDIT: weakened constraint in recursive given Unboxer from Monad to Applicative)
Is this a reasonable approach or is there any simpler way? How would I proceed if I wanted to further abstract this traversal pattern - e.g. should I try to blend in with
Tuple#Map and friends? Is there any library that already does the heavy lifting for me? Is there any deep-dive documentation on this kind of tuple transmogrification available? Any hints appreciated.