web-dev-qa-db-ja.com

Scala二重定義(2つのメソッドが同じ型消去を持っている)

私はこれをscalaで記述しましたが、コンパイルできません:

class TestDoubleDef{
  def foo(p:List[String]) = {}
  def foo(p:List[Int]) = {}
}

コンパイラは通知します:

[error] double definition:
[error] method foo:(List[String])Unit and
[error] method foo:(List[Int])Unit at line 120
[error] have same type after erasure: (List)Unit

JVMにはジェネリックスのネイティブサポートがないため、このエラーを理解しています。

List[String]List[Int]のラッパーを書くことはできましたが、怠惰です:)

疑わしいですが、List[String]List[Int]と同じ型ではありませんか?

ありがとう。

68
Jérôme

暗黙的に使用するというマイケルクレーマーのアイデアが好きですが、より直接的に適用できると思います。

case class IntList(list: List[Int])
case class StringList(list: List[String])

implicit def il(list: List[Int]) = IntList(list)
implicit def sl(list: List[String]) = StringList(list)

def foo(i: IntList) { println("Int: " + i.list)}
def foo(s: StringList) { println("String: " + s.list)}

これは非常に読みやすく、簡単だと思います。

[更新]

動作しているように見える別の簡単な方法があります:

def foo(p: List[String]) { println("Strings") }
def foo[X: ClassManifest](p: List[Int]) { println("Ints") }
def foo[X: ClassManifest, Y: ClassManifest](p: List[Double]) { println("Doubles") }

すべてのバージョンで追加の型パラメーターが必要になるため、これはスケーリングしませんが、3つまたは4つのバージョンでは問題ないと思います。

[更新2]

ちょうど2つの方法で、もう1つの素晴らしいトリックを見つけました。

def foo(list: => List[Int]) = { println("Int-List " + list)}
def foo(list: List[String]) = { println("String-List " + list)}
49
Landei

ダミーの暗黙的な値を作成する代わりに、DummyImplicitで定義されているPredefを使用できます。

class TestMultipleDef {
  def foo(p:List[String]) = ()
  def foo(p:List[Int])(implicit d: DummyImplicit) = ()
  def foo(p:List[Java.util.Date])(implicit d1: DummyImplicit, d2: DummyImplicit) = ()
}
50

型消去の不思議が原因で、メソッドリストの型パラメーターはコンパイル中に消去され、両方のメソッドが同じシグネチャに削減されます。これはコンパイラエラーです。

10
Viktor Klang

MichaelKrämerの解 を理解するには、暗黙的なパラメーターのタイプは重要ではないことを認識する必要があります。 重要なのは重要なのは、それらのタイプが異なることです。

次のコードは同じように機能します。

class TestDoubleDef {
   object dummy1 { implicit val dummy: dummy1.type = this }
   object dummy2 { implicit val dummy: dummy2.type = this }

   def foo(p:List[String])(implicit d: dummy1.type) = {}
   def foo(p:List[Int])(implicit d: dummy2.type) = {}
}

object App extends Application {
   val a = new TestDoubleDef()
   a.foo(1::2::Nil)
   a.foo("a"::"b"::Nil)
}

バイトコードレベルでは、JVMバイトコードは暗黙的なパラメータや複数のパラメータリストを認識しないため、両方のfooメソッドは2つの引数を持つメソッドになります。コールサイトでは、Scalaコンパイラーは、渡されるリストのタイプを調べることにより、呼び出す適切なfooメソッドを(したがって、渡す適切なダミーオブジェクトを)選択します。 (後で消去されるまで消去されません)。

より冗長ですが、このアプローチは、暗黙の引数を提供する負担から呼び出し元を解放します。実際、dummyNオブジェクトがTestDoubleDefクラス専用である場合でも機能します。

10
Aaron Novstrup

Viktor Klangがすでに言っているように、ジェネリック型はコンパイラによって消去されます。幸い、回避策があります。

class TestDoubleDef{
  def foo(p:List[String])(implicit ignore: String) = {}
  def foo(p:List[Int])(implicit ignore: Int) = {}
}

object App extends Application {
  implicit val x = 0
  implicit val y = ""

  val a = new A()
  a.foo(1::2::Nil)
  a.foo("a"::"b"::Nil)
}

先端をありがとう Michid

8
Michel Krämer

ここで Daniel s responseSandor Murakozi sの応答を組み合わせると、次のようになります。

_@annotation.implicitNotFound(msg = "Type ${T} not supported only Int and String accepted")   
sealed abstract class Acceptable[T]; object Acceptable {
        implicit object IntOk extends Acceptable[Int]
        implicit object StringOk extends Acceptable[String]
}

class TestDoubleDef {
   def foo[A : Acceptable : Manifest](p:List[A]) =  {
        val m = manifest[A]
        if (m equals manifest[String]) {
            println("String")
        } else if (m equals manifest[Int]) {
            println("Int")
        } 
   }
}
_

タイプセーフな(ish)バリアントを取得します

_scala> val a = new TestDoubleDef
a: TestDoubleDef = TestDoubleDef@f3cc05f

scala> a.foo(List(1,2,3))
Int

scala> a.foo(List("test","testa"))
String

scala> a.foo(List(1L,2L,3L))
<console>:21: error: Type Long not supported only Int and String accepted
   a.foo(List(1L,2L,3L))
        ^             

scala> a.foo("test")
<console>:9: error: type mismatch;
 found   : Java.lang.String("test")
 required: List[?]
       a.foo("test")
             ^
_

ロジックは、タイプクラスにも含めることができます(おかげで jsuereth )。 Foo [T] {def apply(list:List [T]):Unit}

_object Foo {
   implicit def stringImpl = new Foo[String] {
      def apply(list : List[String]) = println("String")
   }
   implicit def intImpl = new Foo[Int] {
      def apply(list : List[Int]) =  println("Int")
   }
} 

def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
_

それは与える:

_scala> @annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String accepted") 
     | sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo {
     |         implicit def stringImpl = new Foo[String] {
     |           def apply(list : List[String]) = println("String")
     |         }
     |         implicit def intImpl = new Foo[Int] {
     |           def apply(list : List[Int]) =  println("Int")
     |         }
     |       } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
defined trait Foo
defined module Foo
foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit

scala> foo(1)
<console>:8: error: type mismatch;
 found   : Int(1)
 required: List[?]
       foo(1)
           ^    
scala> foo(List(1,2,3))
Int
scala> foo(List("a","b","c"))
String
scala> foo(List(1.0))
<console>:32: error: Foo does not support Double only Int and String accepted
foo(List(1.0))
        ^
_

コンパイラはimplicitly[Foo[A]].apply(x)implicitlyをパラメータで呼び出すことを意味すると考えるため、implicitly[Foo[A]](x)を記述する必要があることに注意してください。

6
oluies

(少なくとも1つは)別の方法があります。それがあまりにもナイスでなく、実際にタイプセーフでなくてもです。

import scala.reflect.Manifest

object Reified {

  def foo[T](p:List[T])(implicit m: Manifest[T]) = {

    def stringList(l: List[String]) {
      println("Strings")
    }
    def intList(l: List[Int]) {
      println("Ints")
    }

    val StringClass = classOf[String]
    val IntClass = classOf[Int]

    m.erasure match {
      case StringClass => stringList(p.asInstanceOf[List[String]])
      case IntClass => intList(p.asInstanceOf[List[Int]])
      case _ => error("???")
    }
  }


  def main(args: Array[String]) {
      foo(List("String"))
      foo(List(1, 2, 3))
    }
}

暗黙のマニフェストパラメーターは、消去されたタイプを「修正」して、消去をハッキングするために使用できます。あなたは多くのブログ投稿でそれについてもう少し学ぶことができます。 これ

何が起こるかというと、マニフェストパラメータはTが消去される前の状態を返すことができるということです。次に、Tに基づく単純なディスパッチによって、さまざまな実際の実装が残りの処理を行います。

おそらくパターンマッチングを行うためのより良い方法があるでしょうが、私はまだそれを見ていません。人々が通常行うことはm.toStringでのマッチングですが、クラスを維持することは少しクリーンです(たとえそれが少し冗長であっても)。残念ながら、マニフェストのドキュメントはあまり詳細ではありません。多分それはそれを簡素化できるものも持っています。

それの大きな欠点は、それが本当にタイプセーフではないことです:fooはどんなTでも満足します、あなたがそれを処理できない場合、あなたは問題を抱えることになります。 Tにいくつかの制約があれば回避できると思いますが、さらに複雑になります。

そしてもちろん、このすべてのものはあまりにも素敵ではありません、それがそれをする価値があるかどうか、特にあなたが怠惰な場合はわかりません;-)

3
Sandor Murakozi

マニフェストを使用する代わりに、同様の方法で暗黙的にインポートされたディスパッチャーオブジェクトを使用することもできます。マニフェストが登場する前に、これについてブログに書きました: http://michid.wordpress.com/code/implicit-double-dispatch-revisited/

これにはタイプセーフティの利点があります。オーバーロードされたメソッドは、現在のスコープにインポートされたディスパッチャーを持つタイプに対してのみ呼び出すことができます。

1
michid

http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.htmlから見つけた素晴らしいトリック by Aaron Novstrup

この死んだ馬をもう少し殴って...

よりクリーンなハックは、シグネチャに消去された型を持つ各メソッドに一意のダミー型を使用することであると思いました:

object Baz {
    private object dummy1 { implicit val dummy: dummy1.type = this }
    private object dummy2 { implicit val dummy: dummy2.type = this } 

    def foo(xs: String*)(implicit e: dummy1.type) = 1
    def foo(xs: Int*)(implicit e: dummy2.type) = 2
} 

[...]

0
Leo

Aaron NovstrupとLeoの回答を改善して、1セットの標準的な証拠オブジェクトをインポート可能にし、簡潔にしました。

final object ErasureEvidence {
    class E1 private[ErasureEvidence]()
    class E2 private[ErasureEvidence]()
    implicit final val e1 = new E1
    implicit final val e2 = new E2
}
import ErasureEvidence._

class Baz {
    def foo(xs: String*)(implicit e:E1) = 1
    def foo(xs: Int*)(implicit e:E2) = 2
}

ただし、その場合、fooが同じ型の暗黙的なパラメーターを必要とする別のメソッドを呼び出すときに、暗黙的な値の選択があいまいであるとコンパイラーが不平を言うようになります。

したがって、私はいくつかのケースでより簡潔な以下だけを提供します。この改善は、値クラス(extend AnyVal)。

final object ErasureEvidence {
   class E1[T] private[ErasureEvidence]()
   class E2[T] private[ErasureEvidence]()
   implicit def e1[T] = new E1[T]
   implicit def e2[T] = new E2[T]
}
import ErasureEvidence._

class Baz {
    def foo(xs: String*)(implicit e:E1[Baz]) = 1
    def foo(xs: Int*)(implicit e:E2[Baz]) = 2
}

含む型の名前がかなり長い場合は、内部のtraitを宣言して簡潔にします。

class Supercalifragilisticexpialidocious[A,B,C,D,E,F,G,H,I,J,K,L,M] {
    private trait E
    def foo(xs: String*)(implicit e:E1[E]) = 1
    def foo(xs: Int*)(implicit e:E2[E]) = 2
}

ただし、値クラスは内部の特性、クラス、オブジェクトを許可しません。したがって、Aaron NovstrupとLeoの回答はバリュークラスでは機能しないことにも注意してください。

0