Scala double definition (2 metody mają ten sam typ kasowania)

Napisałem to w Scali i nie chce się skompilować:

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

Kompilator informuje:

[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

Wiem, że JVM nie ma natywnego wsparcia dla generyków, więc rozumiem ten błąd.

Mógłbym napisać wrappery dla List[String] i List[Int] ale jestem leniwy:)

Wątpię, ale czy jest inny sposób wyrażania List[String] nie jest tego samego typu niż List[Int]?

Dzięki.
Author: giampaolo, 2010-07-22

11 answers

Podoba mi się pomysł Michaela Krämera, aby używać niejawnych, ale myślę, że można je zastosować bardziej bezpośrednio:

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)}
Myślę, że jest to dość czytelne i proste.

[Update]

Jest inny prosty sposób, który wydaje się działać:

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") }

Do każdej wersji potrzebny jest dodatkowy parametr typu, więc nie skaluje się, ale myślę, że dla trzech lub czterech wersji jest w porządku.

[Aktualizacja 2]

Dla dokładnie dwóch metod znalazłem inną ładną trick:

def foo(list: => List[Int]) = { println("Int-List " + list)}
def foo(list: List[String]) = { println("String-List " + list)}
 47
Author: Landei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-16 20:52:48

Zamiast wymyślać ukryte wartości, możesz użyć DummyImplicit zdefiniowanego w Predef, który wydaje się być stworzony właśnie do tego:

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) = ()
}
 44
Author: Jean-Philippe Pellet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-20 20:47:18

Ze względu na cuda usuwania typów, parametry typu listy metod są usuwane podczas kompilacji, redukując w ten sposób obie metody do tej samej sygnatury, co jest błędem kompilatora.

 11
Author: Viktor Klang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-22 10:39:48

Aby zrozumieć rozwiązanie Michaela Krämera , należy uznać, że typy parametrów ukrytych są nieistotne. Co jest ważne jest to, że ich typy są różne.

Następujący kod działa w ten sam sposób:

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)
}

Na poziomie kodu bajtowego obie metody foo stają się metodami dwuargumentowymi, ponieważ JVM bytecode nie wie nic o niejawnych parametrach lub wielu listach parametrów. W miejscu wywołania kompilator Scala wybiera odpowiednie foo metoda wywołania (a zatem odpowiedniego atrapy obiektu do przekazania) poprzez sprawdzenie typu przekazywanej listy (która nie jest usuwana dopiero później).

Chociaż jest to bardziej gadatliwe, takie podejście zwalnia rozmówcę z ciężaru dostarczania ukrytych argumentów. W rzeczywistości działa nawet wtedy, gdy obiekty dummyN są prywatne dla klasy TestDoubleDef.

 10
Author: Aaron Novstrup,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:16

Jak już powiedział Viktor Klang, Typ generyczny zostanie usunięty przez kompilator. Na szczęście istnieje obejście:

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)
}

Dzięki Za Michid za napiwek!

 8
Author: Michel Krämer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-22 11:09:10

Jeśli połączę Daniel s odpowiedź i Sandor Murakozi s odpowiedź dostaję:

@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")
        } 
   }
}

I get a typesafe(ish) variant

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")
             ^

Logika może być również włączona do klasy type jako taka (dzięki jsuereth ): @ adnotacja.implicitNotFound (msg = " Foo nie obsługuje ${t} tylko int i 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)

Co daje:

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))
        ^

Zauważ, że my musimy napisać implicitly[Foo[A]].apply(x) ponieważ kompilator myśli, że implicitly[Foo[A]](x) oznacza, że wywołujemy implicitly z parametrami.

 6
Author: oluies,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:35

Jest (przynajmniej jeden) inny sposób, nawet jeśli nie jest zbyt ładny i nie jest tak naprawdę bezpieczny:

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))
    }
}

Niejawny paramenter manifestu może być użyty do "reify" wymazanego typu i w ten sposób zhakować wymazywanie. Możesz dowiedzieć się więcej na ten temat w wielu wpisach na blogu, np. Ten .

Dzieje się tak, że param manifestu może zwrócić Ci to, co było przed usunięciem. Następnie prosta wysyłka oparta na T do różnych rzeczywistych realizacji robi resztę.

Prawdopodobnie jest ładniejszy sposób na dopasowanie wzoru, ale jeszcze go nie widziałem. To, co ludzie zwykle robią, to dopasowywanie NA m. toString, ale myślę, że prowadzenie zajęć jest trochę czystsze (nawet jeśli jest trochę bardziej gadatliwe). Niestety dokumentacja manifestu nie jest zbyt szczegółowa, może ma również coś, co mogłoby ją uprościć.

Dużą wadą jest to, że nie jest tak naprawdę bezpieczny dla typu: foo będzie zadowolony z każdego T, jeśli nie poradzisz sobie z tym, będziesz miał problem. Myślę, że to może być pracował z pewnymi ograniczeniami na T, ale to jeszcze bardziej skomplikować.

I oczywiście cała ta sprawa też nie jest zbyt miła, nie jestem pewien czy warto to robić, zwłaszcza jeśli jesteś leniwy; -)

 3
Author: Sandor Murakozi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-22 12:40:36

Zamiast używania manifestów można również używać obiektów dispatchers implicite zaimportowanych w podobny sposób. Pisałam o tym zanim pojawiły się manifesty: http://michid.wordpress.com/code/implicit-double-dispatch-revisited/

Ma to zaletę bezpieczeństwa typu: metoda przeciążona będzie wywoływalna tylko dla typów, które mają zaimportowane dyspozytory do bieżącego zakresu.

 1
Author: michid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-26 15:20:31

Próbowałem poprawić odpowiedzi Aarona Novstrupa i Leo, aby jeden zestaw standardowych obiektów dowodowych był importowalny i bardziej zwięzły.

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
}

Ale to spowoduje, że kompilator będzie narzekał, że istnieją niejednoznaczne wybory dla wartości domyślnej, gdy foo wywoła inną metodę, która wymaga parametru domyślnego tego samego typu.

Dlatego oferuję tylko następujące, które są bardziej zwięzłe w niektórych przypadkach. I ta poprawa działa z klasami wartości (tymi, które 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
}

Jeśli nazwa typu zawierającego jest dość długa, zadeklaruj wewnętrzny trait, aby uczynić go bardziej zwięzłym.

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
}

Jednak klasy wartości nie zezwalają na wewnętrzne cechy, klasy ani obiekty. Dlatego też należy zauważyć, że odpowiedzi Aarona Novstrupa i Leo nie działają z klasami wartości.

 0
Author: Shelby Moore III,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-16 07:20:59

Fajny trik znalazłem z http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html Autor: Aaron Novstrup

Pobicie tego martwego konia...

Przyszło mi do głowy, że Cleaner hack to użycie unikalnego typu atrapy dla każdej metody z usuniętymi typami w sygnaturze:

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
} 

[...]

 -1
Author: Leo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-20 12:36:20

Nie testowałem tego, ale dlaczego górna granica nie działa?

def foo[T <: String](s: List[T]) { println("Strings: " + s) }
def foo[T <: Int](i: List[T]) { println("Ints: " + i) }

Czy tłumaczenie kasowania zmienia się dwukrotnie z foo( List[any] s ) na foo( List[String] s ) i foo( List[Int] i):

Http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108

Myślę, że czytałem, że w wersji 2.8, górne granice są teraz zakodowane w ten sposób, zamiast zawsze an.

Aby przeciążyć typy kowariantne, użyj wiązania niezmienniczego (czy istnieje taka składnia w Scali?...ah myślę, że nie ma, ale weź następujące jako dodatek koncepcyjny do głównego rozwiązania powyżej):

def foo[T : String](s: List[T]) { println("Strings: " + s) }
def foo[T : String2](s: List[T]) { println("String2s: " + s) }
W takim razie zakładam, że dorozumiane odlewanie jest eliminowane w wymazanej wersji kodu.

UPDATE: problem polega na tym, że JVM kasuje więcej informacji o typie podpisów metod niż jest "konieczne". Podałem link. Wymazuje zmienne typu z konstruktorów typu, nawet konkretną granicę tych zmiennych typu. Istnieje pojęcie rozróżnienie, ponieważ nie ma pojęciowej nieefifikowanej przewagi w usuwaniu wiązań typu funkcji, jak to jest znane w czasie kompilacji i nie różni się od żadnego wystąpienia generycznego, i konieczne jest, aby wywołujący nie wywoływali funkcji z typami, które nie są zgodne z wiązaniem typu, więc jak JVM może wymusić Wiązanie typu, jeśli zostanie usunięte? Cóż jeden link mówi, że powiązanie typu jest przechowywane w metadanych, do których Kompilatory mają dostęp. I to wyjaśnia dlaczego używanie typu ograniczenia nie umożliwiają przeciążenia. Oznacza to również, że JVM jest szeroko otwartą dziurą bezpieczeństwa, ponieważ metody ograniczające typy mogą być wywoływane bez ograniczeń typów (yikes!), więc przepraszam za założenie, że projektanci JVM nie zrobiliby tak niepewnej rzeczy.

Kiedy to pisałem, nie rozumiałem, że stackoverflow był systemem oceniania ludzi po Jakości odpowiedzi, jak jakaś konkurencja nad reputacją. Myślałem, że to miejsce do dzielenia się informacjami. Kiedy to pisałem, porównywałem reified i non-reified z poziomu konceptualnego (porównując wiele różnych języków), a więc w moim umyśle nie miało sensu kasowanie typu bound.

 -2
Author: Shelby Moore III,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:02