Как определить «дизъюнкцию типа» (объединение типов)?

Был предложен один из способов иметь дело с двойными определениями перегруженных методов - это заменить перегрузку сопоставлением с шаблоном:

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 не имеет встроенной «дизъюнкции типа». Кроме того, неявные преобразования, определенные выше, скрываются где-то в стандартной библиотеке, чтобы я мог их просто импортировать?

175
задан Community 23 May 2017 в 12:18
поделиться

6 ответов

Что ж, в конкретном случае 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 просматривает сопутствующие объекты типа, чтобы увидеть, есть ли там имплициты чтобы заставить работать код, запрашивающий этот тип.

137
ответ дан 23 November 2019 в 20:25
поделиться

Решение с типовым классом, вероятно, лучший способ здесь использовать имплициты. Это похоже на моноидный подход, упомянутый в книге Одерски / Спун / Веннерс:

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)

              ^
12
ответ дан 23 November 2019 в 20:25
поделиться

Есть также этот хак :

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)

См. Работа с неоднозначностями стирания типа (Scala) .

8
ответ дан 23 November 2019 в 20:25
поделиться

Можно обобщить решение Дэниела следующим образом:

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

Основными недостатками этого подхода являются

  • Как отметил Дэниел, он не обрабатывает коллекции/varargs со смешанными типами
  • Компилятор не выдает предупреждение, если соответствие не является исчерпывающим
  • Компилятор не выдает ошибку, если соответствие включает невозможный случай
  • Как и в случае с Either, дальнейшее обобщение потребует определения аналогичных Or3, Or4 и т.д. признаков. признаков. Конечно, определение таких признаков будет намного проще, чем определение соответствующих классов Either.

Обновление:

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

17
ответ дан 23 November 2019 в 20:25
поделиться

Вы можете взглянуть на MetaScala , в котором есть нечто, называемое OneOf . У меня сложилось впечатление, что это плохо работает с операторами match , но что вы можете моделировать сопоставление с помощью функций высшего порядка. Взгляните, например, на этот фрагмент , но обратите внимание, что часть «имитация сопоставления» закомментирована, возможно потому, что она еще не совсем работает.

Теперь небольшая редакционная статья: я не думаю, что есть что-то вопиющее в определении Either3, Either4 и т. Д., Как вы описываете. Это по сути двойственно стандартным 22 типам кортежей, встроенным в Scala. Было бы неплохо, если бы в Scala были встроенные дизъюнктивные типы и, возможно, какой-нибудь приятный синтаксис для них, например {x, y, z} .

7
ответ дан 23 November 2019 в 20:25
поделиться

Что ж, это все очень умно, но я почти уверен, что вы уже знаете, что ответы на ваши наводящие вопросы - это различные варианты «Нет». Scala по-другому обрабатывает перегрузку и, надо признать, несколько менее элегантно, чем вы описываете. Отчасти это связано с совместимостью с Java, отчасти из-за нежелания затрагивать крайние варианты алгоритма вывода типов, а некоторые из-за того, что он просто не является Haskell.

1
ответ дан 23 November 2019 в 20:25
поделиться
Другие вопросы по тегам:

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