Tuple mapping/traversal in Scala 3

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.

1 Like

You can do

type Unboxed[B] = B match
  case Box[f, a] => f[a]

val result: Option[(Int, String)] =
  boxes.map[Unboxed](
    [a] => (box: a) => box.asInstanceOf[Box[?, ?]].unbox.asInstanceOf[Unboxed[a]]
  )
  .mapN((_, _))

I haven’t seen any way that you can work with those fancy tuple methods or implement new ones without a lot of casting…
Shapeless is supposed to have a Scala 3 version with a completely reworked API. I would hope that you can find a well typed API there that can do all these things.

Thanks a lot! Hmm, this is certainly much less convoluted than my approach (which, as I realized in the meantime, is a variant of a technique I “discovered” some time ago). But the casts really scare me off. :confused: The match type cast is a neat trick, though. :slight_smile:

It looks like what’s there already is mostly focusing on auto derivation for now.