Двойное определение Scala (2 метода имеют стирание одного типа) - PullRequest
65 голосов
/ 22 июля 2010

Я написал это в 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]?

Спасибо.

Ответы [ 11 ]

49 голосов
/ 21 апреля 2011

Вместо того, чтобы придумывать фиктивные неявные значения, вы можете использовать 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) = ()
}
48 голосов
/ 22 июля 2010

Мне нравится идея Михаила Крамера использовать импликации, но я думаю, что она может быть применена более непосредственно:

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

Я думаю, что это вполне читабельно и просто.

[Update]

Есть еще один простой способ, который работает:

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

Для каждой версии вам нужен дополнительный параметр типа, поэтому он не масштабируется, но я думаю, что для трех или четырех версий это нормально.

[Обновление 2]

Для ровно двух методов я нашел еще один приятный трюк:

def foo(list: => List[Int]) = { println("Int-List " + list)}
def foo(list: List[String]) = { println("String-List " + list)}
10 голосов
/ 23 августа 2010

Чтобы понять решение Михаэля Крамера , необходимо признать, что типы неявных параметров не важны.Что важно для *, так это то, что их типы различны.

Следующий код работает аналогично:

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

На уровне байт-кода оба метода foo становятся методами с двумя аргументами, поскольку байт-код JVM ничего не знает о неявных параметрах или нескольких параметрахсписки.На месте вызова компилятор Scala выбирает соответствующий метод foo для вызова (и, следовательно, соответствующий фиктивный объект для передачи), просматривая тип передаваемого списка (который не удаляется до позднего времени).

Хотя это более многословно, этот подход освобождает вызывающего абонента от бремени предоставления неявных аргументов.Фактически, это даже работает, если объекты dummyN являются частными для класса TestDoubleDef.

10 голосов
/ 22 июля 2010

Из-за чудес стирания типов параметры типа списка ваших методов стираются во время компиляции, таким образом сводя оба метода к одной и той же сигнатуре, что является ошибкой компилятора.

8 голосов
/ 22 июля 2010

Как уже сказал Виктор Кланг, универсальный тип будет удален компилятором. К счастью, есть обходной путь:

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

Спасибо за Михид за отзыв!

6 голосов
/ 26 сентября 2010

Если я объединю Даниэль с ответ и Сандор Муракози ответ здесь я получаю:

@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 ): @ annotation.implicitNotFound (msg = "Foo не поддерживает $ {T}, только Int и String приняты") Запечатанная черта Foo [T] {def apply (список: список [T]): Единица}

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[Foo[A]](x) означает, что мы вызываем implicitly с параметрами.

3 голосов
/ 22 июля 2010

Существует (по крайней мере, один) другой способ, даже если он не слишком приятен и не совсем безопасен для типов:

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

Неявный параметр манифеста можно использовать для «повторного определения» стертого типа итаким образом взломать вокруг стирания.Вы можете узнать немного больше об этом во многих сообщениях в блогах, например, this .

. Что происходит, так это то, что явный параметр может вернуть вам то, что было до удаления.Тогда простая отправка, основанная на T, для различных реальных реализаций сделает все остальное.

Вероятно, есть более хороший способ сопоставления с образцом, но я еще не видел его.То, что обычно делают люди, это сопоставление с m.toString, но я думаю, что хранить классы немного чище (даже если это немного более многословно).К сожалению, документация Манифеста не слишком детализирована, возможно, в ней также есть что-то, что может упростить ее.

Большим недостатком является то, что он не совсем безопасен для типов: foo будет доволен любым T, если вы не справитесь с этим, у вас будут проблемы.Я думаю, что это можно обойти с некоторыми ограничениями на T, но это еще больше усложнит это.

И, конечно, все это тоже не слишком приятно, я не уверен, стоит ли это делать, особенно если вы ленивы; -)

1 голос
/ 26 июля 2010

Вместо использования манифестов вы также можете использовать диспетчерские объекты, неявно импортированные аналогичным образом.Я писал об этом до того, как появились манифесты: http://michid.wordpress.com/code/implicit-double-dispatch-revisited/

Это имеет преимущество безопасности типов: перегруженный метод будет вызываться только для типов, у которых диспетчеры импортированы в текущую область.

0 голосов
/ 16 ноября 2014

Я попытался улучшить ответы Аарона Новструпа и Лео, чтобы сделать один набор стандартных объектов доказательств импортируемым и более кратким.

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
}

Однако классы значений не допускают внутренних черт, классов или объектов. Также обратите внимание, что ответы Аарона Новструпа и Лео не работают с классами значений.

0 голосов
/ 20 февраля 2012

Хороший трюк, который я нашел из http://scala -programming-language.1934581.n4.nabble.com Аарон Новструп

Победи эту мертвую лошадь еще немного ...

Мне пришло в голову, что более чистый взлом заключается в использовании уникального типа пустышки для каждого метода со стертыми типами в его подписи:

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
} 

[...]

...