Был предложен один из способов иметь дело с двойными определениями перегруженных методов - это заменить перегрузку сопоставлением с шаблоном:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
Этот подход требует, чтобы мы отказались от статической проверки типов в аргументах foo
. Было бы гораздо приятнее написать
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
, с которым я могу подобраться Либо
, но это получается ужасно быстро с более чем двумя типами:
type or[L,R] = Either[L,R]
implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case Left(l) => println("str")
case Right(r) => println("int")
}
}
Это выглядит как общий (элегантный, эффективный) решение потребует определения Either3
, Either4
, .... Кто-нибудь знает альтернативное решение для достижения той же цели? Насколько мне известно, Scala не имеет встроенной «дизъюнкции типа». Кроме того, неявные преобразования, определенные выше, скрываются где-то в стандартной библиотеке, чтобы я мог их просто импортировать?
Что ж, в конкретном случае Any *
этот трюк ниже не сработает, так как он не принимает смешанные типы. Однако, поскольку смешанные типы также не будут работать с перегрузкой, это может быть то, что вам нужно.
Сначала объявите класс с типами, которые вы хотите принять, как показано ниже:
class StringOrInt[T]
object StringOrInt {
implicit object IntWitness extends StringOrInt[Int]
implicit object StringWitness extends StringOrInt[String]
}
Затем объявите foo
следующим образом:
object Bar {
def foo[T: StringOrInt](x: T) = x match {
case _: String => println("str")
case _: Int => println("int")
}
}
И все. Вы можете вызвать foo (5)
или foo ("abc")
, и это будет работать, но попробуйте foo (true)
, и это не удастся. Это можно обойти с помощью клиентского кода, создав StringOrInt [Boolean]
, если, как указано ниже Randall , вы не сделаете StringOrInt
a ] герметичный
класс.
Это работает, потому что T: StringOrInt
означает, что существует неявный параметр типа StringOrInt [T]
, и потому, что Scala просматривает сопутствующие объекты типа, чтобы увидеть, есть ли там имплициты чтобы заставить работать код, запрашивающий этот тип.
Решение с типовым классом, вероятно, лучший способ здесь использовать имплициты. Это похоже на моноидный подход, упомянутый в книге Одерски / Спун / Веннерс:
abstract class NameOf[T] {
def get : String
}
implicit object NameOfStr extends NameOf[String] {
def get = "str"
}
implicit object NameOfInt extends NameOf[Int] {
def get = "int"
}
def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get)
Если вы затем запустите это в REPL:
scala> printNameOf(1)
int
scala> printNameOf("sss")
str
scala> printNameOf(2.0f)
<console>:10: error: could not find implicit value for parameter nameOf: NameOf[
Float]
printNameOf(2.0f)
^
Есть также этот хак :
implicit val x: Int = 0
def foo(a: List[Int])(implicit ignore: Int) { }
implicit val y = ""
def foo(a: List[String])(implicit ignore: String) { }
foo(1::2::Nil)
foo("a"::"b"::Nil)
Можно обобщить решение Дэниела следующим образом:
sealed trait Or[A, B]
object Or {
implicit def a2Or[A,B](a: A) = new Or[A, B] {}
implicit def b2Or[A,B](b: B) = new Or[A, B] {}
}
object Bar {
def foo[T <% String Or Int](x: T) = x match {
case _: String => println("str")
case _: Int => println("int")
}
}
Основными недостатками этого подхода являются
Either
, дальнейшее обобщение потребует определения аналогичных Or3
, Or4
и т.д. признаков. признаков. Конечно, определение таких признаков будет намного проще, чем определение соответствующих классов Either
. Обновление:
Митч Блевинсдемонстрирует очень похожий подход и показывает, как обобщить его на более чем два типа, назвав его "заикающимся или".
Вы можете взглянуть на MetaScala , в котором есть нечто, называемое OneOf
. У меня сложилось впечатление, что это плохо работает с операторами match
, но что вы можете моделировать сопоставление с помощью функций высшего порядка. Взгляните, например, на этот фрагмент , но обратите внимание, что часть «имитация сопоставления» закомментирована, возможно потому, что она еще не совсем работает.
Теперь небольшая редакционная статья: я не думаю, что есть что-то вопиющее в определении Either3, Either4 и т. Д., Как вы описываете. Это по сути двойственно стандартным 22 типам кортежей, встроенным в Scala. Было бы неплохо, если бы в Scala были встроенные дизъюнктивные типы и, возможно, какой-нибудь приятный синтаксис для них, например {x, y, z}
.
Что ж, это все очень умно, но я почти уверен, что вы уже знаете, что ответы на ваши наводящие вопросы - это различные варианты «Нет». Scala по-другому обрабатывает перегрузку и, надо признать, несколько менее элегантно, чем вы описываете. Отчасти это связано с совместимостью с Java, отчасти из-за нежелания затрагивать крайние варианты алгоритма вывода типов, а некоторые из-за того, что он просто не является Haskell.