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 Some((42, "foo"))
.
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.