Search code examples
scalagenericstuplesshapelessscala-3

Extract a subset of a tuple in Scala 3


Given an arbitrary tuple, I want to extract a subset of the tuple. The signature would look something like:

def subset[T1 <: Tuple, T2 <: Tuple](t:T1): T2 = ???

where T2 is a tuple with some subset of members chosen from T1.

Using it would look something like

subset[(String, Int, Boolean), (String, Boolean)]( ("str", 42, true) ) == ("str", true)

I understand that this requires extensive use of match types and typelevel programming. I feel like it be pretty straight forward with Shapeless but I'm missing some functionality from the std lib on tuples vs HLIST.


Solution

  • This looks like a job for typeclasses! (note that the only Scala 3-specific feature required is *:)

    def subset[T1 <: Tuple, T2 <: Tuple](t: T1)(using s: Subset[T1, T2]): T2 = s(t)
    
    opaque type Subset[T1 <: Tuple, T2 <: Tuple] = T1 => T2
    object Subset:
      given [T1 <: Tuple]: Subset[T1, EmptyTuple] = _ => EmptyTuple
      given [A, T1 <: Tuple, T2 <: Tuple](using s: Subset[T1, T2]): Subset[A *: T1, A *: T2] =
        case a *: t => a *: s(t)
      given [A, T1 <: Tuple, T2 <: Tuple](using s: Subset[T1, T2]): Subset[A *: T1, T2] =
        case _ *: t => s(t)
    

    See it in Scastie

    If you want it to work regardless of order, it's slightly more complicated but still manageable:

    opaque type Find[T <: Tuple, E] = T => E
    object Find:
      given [T <: Tuple]: Find[T, EmptyTuple] = _ => EmptyTuple
      given [A, T <: Tuple]: Find[A *: T, A] = 
        case a *: _ => a
      given [A, H, T <: Tuple](using f: Find[T, A]): Find[H *: T, A] =
        case _ *: t => f(t)
    
    opaque type Subset[T1 <: Tuple, T2 <: Tuple] = T1 => T2
    object Subset:
      given [T1 <: Tuple]: Subset[T1, EmptyTuple] = _ => EmptyTuple
      given [A, T1 <: Tuple, T2 <: Tuple](using 
          s: Subset[T1, T2], 
          f: Find[T1, A]
      ): Subset[T1, A *: T2] =
        t => f(t) *: s(t)
    
    def subset[T1 <: Tuple, T2 <: Tuple](t: T1)(using s: Subset[T1, T2]): T2 = s(t)
    

    See it in Scastie