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]?

Спасибо.

67
задан giampaolo 29 September 2015 в 12:48
поделиться

6 ответов

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

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

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

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

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

def foo(list: => List[Int]) = { println("Int-List " + list)}
def foo(list: List[String]) = { println("String-List " + list)}
50
ответ дан 24 November 2019 в 14:35
поделиться

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

10
ответ дан 24 November 2019 в 14:35
поделиться

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

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
ответ дан 24 November 2019 в 14:35
поделиться

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

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

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

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

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

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

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

.
3
ответ дан 24 November 2019 в 14:35
поделиться

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

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

1
ответ дан 24 November 2019 в 14:35
поделиться

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

Следующий код работает таким же образом:

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
ответ дан 24 November 2019 в 14:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: