How to define overload methods based on type parameters

I am attempting to generate an API in Scala 3 that concatenates Iterable of Tuple. Say I have the following functions that zip these Iterable

  def and[A,B](a1: Iterable[A], a2: Iterable[B])
               (implicit d: DummyImplicit): Iterable[(A,B)] = 
      a1.zip(a2)
  def and[A<:Tuple,B<:Tuple](a1: Iterable[A], a2: Iterable[B])
             (implicit d1: DummyImplicit, d2: DummyImplicit): 
  Iterable[Tuple.Concat[A,B]] =
    a1.zip(a2).map( (a,b) => a ++ b)

No I can do this:

    val s1 = 1 to 3 by 1
    val s2 = 1 to 6 by 2
    val s3 = 'a' to 'f' 

    val r1: Iterable[(Int, Int)] = and(s1,s2)
    println(r1)

    val r2: Iterable[(Int, Int, Int, Int)] = and(r1, r1)
    println(r2)

However, this won’t compile:


    val r4: Iterable[(Char, Int, Int)] = and( s3, r1)
    println(r4)

generating this error:

error] 510 |    val r4: Iterable[(Char, Int, Int)] = and( s3, r1)
[error]     |                                         ^^^^^^^^^^^^
[error]     |                                Found:    Iterable[(Char, (Int, Int))]
[error]     |                                Required: Iterable[(Char, Int, Int)]
[error] one error found

The problem here is that I would like the user not be require to convert a single non-tuple element to any Tuple1. So I added this function:

  def and[A,B<:Tuple](a1: Iterable[A], a2: Iterable[B])
                     (implicit d1: DummyImplicit, 
                                  d2: DummyImplicit, 
                                  d3: DummyImplicit): 
  Iterable[*:[A,B]] =
    a1.zip(a2).map( (a,b) => a *: b)

Note that I am using dummy implicits to “circumvent” the double definition as discusses here.

The above will also not allow compilation, because now the type A can successfully match a single element or a tuple. I then tried avoiding such as much using the following code:

  trait =!=[A, B]
  implicit def neq[A, B] : A =!= B = null
  // This pair excludes the A =:= B case
  implicit def neqAmbig1[A] : A =!= A = null
  implicit def neqAmbig2[A] : A =!= A = null

  def and[A,B<:Tuple](a1: Iterable[A], a2: Iterable[B])
                     (implicit d1: DummyImplicit, 
                                  d2: DummyImplicit, 
                                  d3: DummyImplicit, 
                                  ev: A =!= Tuple): Iterable[*:[A,B]] =
    a1.zip(a2).map( (a,b) => a *: b)

and that still fails with

error] 507 |    val r2: Iterable[(Int, Int, Int, Int)] = and(r1, r1)
[error]     |                                                 ^^
[error]     |                                 Found:    (r1 : Iterable[(Int, Int)])
[error]     |                                 Required: Iterable[Int]
[error] -- [E007] Type Mismatch Error: /home/hmf/IdeaProjects/pdm_toyadmos/dcase2020/test/src/data/synthetic/TimeSeriesSpec.scala:507:53 
[error] 507 |    val r2: Iterable[(Int, Int, Int, Int)] = and(r1, r1)
[error]     |                                                     ^^
[error]     |                                 Found:    (r1 : Iterable[(Int, Int)])
[error]     |                                 Required: Iterable[(Int, Int, Int)]
[error] two errors found

So my question is, is this doable and if so how?

TIA,
HF

1 Like

I haven’t fully read and understood the entire question, but note that the “implicit ambiguity” trick isn’t supposed to work anymore in Scala 3. That’s why scala.util.Not was added. type =!=[A, B] = Not[A =:= B].

Also note that it might get renamed to NotGiven.

@Jasper-M Thanks for the heads-up. Do you know if their is any similar utility to check for “not a subtype of”? In the dotty gitter channel I was informed (and it makes sense) that I should test subtyping and not equality.

TIA,

<:< test the subtype relation, so probably Not[A <:< B]. That should mean A is not a subtype of B.

1 Like

I have come up with a simpler example:

  import scala.util.Not

  def func3[A,B](a : A, b : B)(implicit ev1: Not[A<:<Tuple], ev2: Not[B<:<Tuple]): (A,B) = (a,b)
  def func3[A,B<:Tuple](a : A, b : B)(implicit ev: Not[A<:<Tuple]): *:[A,B] = a *: b
  def func3[A<:Tuple,B](a : A, b : B)(implicit ev: Not[B<:<Tuple]): Tuple.Concat[A,Tuple1[B]] = a ++ Tuple1(b)
  def func31[A<:Tuple,B](a : A, b : B)(implicit ev: Not[B<:<Tuple]): Tuple.Concat[A,Tuple1[B]] = a ++ Tuple1(b)
  def func3[A<:Tuple,B<:Tuple](a : A, b : B): Tuple.Concat[A,B] = a ++ b

    val d10_2: *:[Int, *:[Double, *:[String, *:[Char, EmptyTuple]]]] = (1,2.0,"3") ++ Tuple1('4')
    // TODO: this fails
    //val d10_3: *:[Int, *:[Double, *:[String, *:[Char, EmptyTuple]]]] = func3((1,2.0,"3"), '4')
    //val d10_3: (Int, Double, String, Char) = func3((1,2.0,"3"), '4')
    val d10_3 = func3((1,2.0,"3"), '4')
    val d10_3_1: Int = d10_3(0)
    val d10_3_2: Double = d10_3(1)
    val d10_3_3: String = d10_3(2)
    val d10_3_4: Char = d10_3(3)

Using the explicit type breaks. However if I rename the method, it works:

    val d10_3a: (Int, Double, String, Char) = func31((1,2.0,"3"), '4')

Just in case this is a Dotty issue: