Scala double definition (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] ?

Благодарю.

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

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

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

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

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

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

 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 являются частными для classа TestDoubleDef .

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

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

Спасибо за Мичид за подсказку!

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

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

Я получаю вариант типа (иш)

 scala> val a = new TestDoubleDef a: TestDoubleDef = [email protected] scala> a.foo(List(1,2,3)) Int scala> a.foo(List("test","testa")) String scala> a.foo(List(1L,2L,3L)) :21: error: Type Long not supported only Int and String accepted a.foo(List(1L,2L,3L)) ^ scala> a.foo("test") :9: error: type mismatch; found : java.lang.String("test") required: List[?] a.foo("test") ^ 

Логика также может быть включена в class типа как таковой (благодаря 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) :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)) :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 называем параметрами.

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

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

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

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

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

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

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

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

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

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

 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 вызывает другой метод, который требует неявного параметра того же типа.

Таким образом, я предлагаю только следующее, что в некоторых случаях является более кратким. И это улучшение работает со значениями classов (те, которые 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 } 

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

Хороший трюк, который я нашел из http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html от 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 } 

[…]

Я не тестировал это, но почему бы не работать над верхней границей?

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

Переводит ли перевод стирания из foo (List [Any] s) дважды в foo (List [String] s) и foo (List [Int] i):

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

Я думаю, что прочитал, что в версии 2.8 верхние границы теперь закодированы таким образом, а не всегда.

Чтобы перегрузить ковариантные типы, используйте инвариантную границу (есть ли такой синтаксис в Scala? ... ах, я думаю, что нет, но в качестве концептуального дополнения к главному решению выше)

 def foo[T : String](s: List[T]) { println("Strings: " + s) } def foo[T : String2](s: List[T]) { println("String2s: " + s) } 

то я предполагаю, что неявное литье устраняется в стертой версии кода.


ОБНОВЛЕНИЕ: проблема в том, что JVM стирает больше информации о типе в сигнатурах методов, чем «необходимо». Я предоставил ссылку. Он стирает переменные типа от конструкторов типов, даже конкретную оценку этих переменных типа. Существует концептуальное различие, поскольку нет концептуального неосуществленного преимущества для стирания связанного типа функции, поскольку оно известно во время компиляции и не зависит от какого-либо экземпляра родового, и для звонящих не требуется звонить функция с типами, которые не соответствуют привязке типа, поэтому как JVM может принудительно применять привязку типа, если она стирается? Ну, одна ссылка говорит, что привязка типа сохраняется в метаданных, к которым должны обращаться компиляторы. И это объясняет, почему использование ограничений типов не позволяет перегружать. Это также означает, что JVM представляет собой широко открытое отверстие безопасности, так как ограниченные типы типов можно вызывать без ограничений типов (yikes!), Поэтому извините меня за то, что дизайнеры JVM не будут делать такую ​​небезопасную вещь.

В то время, когда я писал это, я не понимал, что stackoverflow - это система оценки людей по качеству ответов, таких как конкуренция за репутацию. Я думал, что это место для обмена информацией. В то время, когда я писал это, я сравнивал reified и non-reified с концептуального уровня (сравнивая многие разные языки), и поэтому, на мой взгляд, не было смысла стирать привязку типа.

Interesting Posts

Могу ли я объединить: nth-child () или: nth-of-type () с произвольным селектором?

Как проверить AppleScript, если приложение запущено, без его запуска – с помощью утилиты osascript

Количество месяцев между двумя датами

Является gcc 4.8 или более ранней ошибкой регулярных выражений?

Почему мой компьютер с Windows 8 всегда работает, перемещая мышь?

Можно ли использовать новую файловую систему ReFS в Windows 8?

Атрибут на интерфейсных элементах не работает

Как динамически создавать столбцы в DataGrid WPF?

Есть ли способ получить все пары имени / значения querystring в коллекции?

Создание кадра данных с неравными длинами

Что такое идентификатор клиента при отправке данных отслеживания в Google Analytics через протокол измерений?

Что более эффективно, для каждого цикла или для iteratorа?

Являются ли бортовые контроллеры рейдов на базе южного моста Intel ICH10R по-прежнему считаются программным обеспечением Raid?

Изменить несколько значений сразу

Как работает Skype без переадресации портов?

Давайте будем гением компьютера.