Search code examples
scalatuplesshapelessdottyscala-3

How to combine two tuples with compatible types?


Suppose I have two tuples, the first is a tuple of values with type (V1, V2, .., Vn),

the second is a tuple of functions with type (V1 => V1, V2 => V2, .., Vn => Vn).

Now I want to combine the two tuples as (f1(v1), v2(v2), .., fn(vn)) with type (V1, V2, .., Vn).

scala> val values = (1, 2.0, "3")
val values: (Int, Double, String) = (1,2.0,3)

scala> val funs = ((i: Int) => 2 * i, (f: Double) => 2 * f, (s: String) => s * 2)
val funs: (Int => Int, Double => Double, String => String) = ..

scala> val res = ???  // (2, 4.0, "33")

I have no idea how to get this in scala 3.0 (i.e. dotty).

EDIT: I look into the source code of shapeless and got a (partial work) solution:

scala> trait Zip[V <: Tuple, F <: Tuple]{ type R <: Tuple; def apply(v: V, f: F): R }

scala> given Zip[Unit, Unit]{ type R = Unit; def apply(v: Unit, f: Unit): Unit = () }

scala> given [Hv, Hr, V <: Tuple, F <: Tuple](given z: Zip[V, F]): Zip[Hv *: V, (Hv => Hr) *: F] = new Zip {
     |   type R = Hr *: z.R
     |   def apply(v: Hv *: V, f: (Hv => Hr) *: F): R = {
     |     f.head(v.head) *: z.apply(v.tail, f.tail)
     |   }
     | }

scala> val values = (1, 2.0, "3")
val values: (Int, Double, String) = (1,2.0,3)

scala> val funs = ((i: Int) => 2 * i, (f: Double) => 2 * f, (s: String) => s * 2)
val funs: (Int => Int, Double => Double, String => String) = ..

scala> def apply[V <: Tuple, F <: Tuple](v: V, f: F)(given z: Zip[V, F]): z.R = z.apply(v, f)
def apply[V <: Tuple, F <: Tuple](v: V, f: F)(given z: Zip[V, F]): z.R

scala> apply(values, funs)
val res0:
  Zip[(Int, Double, String), (Int => Int, Double => Double, String => String)]#R = (2,4.0,33)

scala> val res: (Int, Double, String) = apply(values, funs)
1 |val res: (Int, Double, String) = apply(values, funs)
  |                                 ^^^^^^^^^^^^^^^^^^^
  |Found:    ?1.R
  |Required: (Int, Double, String)
  |
  |where:    ?1 is an unknown value of type Zip[(Int, Double, String), (Int => Int, Double => Double, String => String)]

I do not known why the return of apply method lost its type.


Solution

  • why the return of apply method lost its type

    This is because you lost type refinement (this behavior is similar in Scala 2 and Dotty).

    The code

    given [Hv, Hr, V <: Tuple, F <: Tuple](given z: Zip[V, F]): Zip[Hv *: V, (Hv => Hr) *: F] = new Zip {
      ...
    

    should be

    given [Hv, Hr, V <: Tuple, F <: Tuple](given z: Zip[V, F]): (Zip[Hv *: V, (Hv => Hr) *: F] { type R = Hr *: z.R }) = new Zip[Hv *: V, (Hv => Hr) *: F] {
      ...
    

    or with Aux pattern

    given [Hv, Hr, V <: Tuple, F <: Tuple](given z: Zip[V, F]): Zip.Aux[Hv *: V, (Hv => Hr) *: F, Hr *: z.R] = new Zip[Hv *: V, (Hv => Hr) *: F] {
      ...
    

    Tested in 0.21.0-RC1.