Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
512 views
in Technique[技术] by (71.8m points)

scala - How can I differentiate between def foo[A](xs: A*) and def foo[A, B](xs: (A, B)*)?

I know that type erasure makes them look equal, type-wise, at runtime, so that:

class Bar {
    def foo[A](xs: A*) { xs.foreach(println) }
    def foo[A, B](xs: (A, B)*) { xs.foreach(x => println(x._1 + " - " + x._2)) }
}   

gives the following compiler error:

<console>:7: error: double definition:
method foo:[A,B](xs: (A, B)*)Unit and
method foo:[A](xs: A*)Unit at line 6
have same type after erasure: (xs: Seq)Unit
        def foo[A,B](xs: (A, B)*) { xs.foreach(x => println(x._1 + " - " + x._2)
) }
            ^

But is there a simple way to be able to write:

bar.foo(1, 2, 3)
bar.foo(1 -> 2, 3 -> 4)

and having these call different overloaded versions of foo, without having to explicitly name them:

bar.fooInts(1, 2, 3)
bar.fooPairs(1 -> 2, 3 -> 4)
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can, in a fairly round about way. Foo is a type class, and the compiler implcitly passes an instance of the type class, compatible with the (inferred) type parameter A.

trait Foo[X] {
  def apply(xs: Seq[X]): Unit
}

object Foo {
 implicit def FooAny[A]: Foo[A] = new Foo[A] {
    def apply(xs: Seq[A]) = println("apply(xs: Seq[A])")
  }
  implicit def FooTuple2[A, B]: Foo[(A, B)] = new Foo[(A, B)] {
    def apply(xs: Seq[(A, B)]) = println("apply(xs: Seq[(A, B)])")
  }

  def apply[A](xs: A*)(implicit f: Foo[A]) = f(xs)
}


Foo(1, 2, 3)        // apply(xs: Seq[A])
Foo(1 -> 2, 2 -> 3) // apply(xs: Seq[(A, B)])

In the second call, both FooAny and FooTuple2 could be passed, but the compiler picks FooTuple2, based on the rules of static method overloading. FooTuple2 is considered more specific that FooAny. If two candidates are considered to be as specific as each other, an ambiguity error is raised. You can also prefer one over the other by placing one in a superclass, as is done in scala.LowPriorityImplicits.

UPDATE

Riffing off the DummyImplicit idea, and the followup thread on scala-user:

trait __[+_]
object __ {
 implicit object __ extends __[Any]
}

object overload {
 def foo(a: Seq[Boolean]) = 0

 def foo[_: __](a: Seq[Int]) = 1

 def foo[_: __ : __](a: Seq[String]) = 2
}

import overload._
foo(Seq(true)) 
foo(Seq(1)) 
foo(Seq("s")) 

This declares a type-parameterized trait __, covariant in its unnamed type parameter _. Its companion object __ contains an implicit instance of __[Any], which we'll need later on. The second and third overloads of foo include a dummy type parameters, again unnamed. This will be inferred as Any. This type parameter has one or more context bounds, which are desugared into additional implicit parameters, for example:

 def foo[A](a: Seq[Int])(implicit ev$1: __[A]) = 1

The multiple parameter lists are concatenated into a single parameter list in the bytecode, so the double definition problem is circumvented.

Please consider this as a opportunity to learn about erasure, context bounds and implicit search, rather than as a pattern to be applied in real code!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...