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

165

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

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

Я могу приблизиться к Either, но он становится уродливым быстрее с более чем двумя типами:

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

Теги:

15 ответов

128
Лучший ответ

Ну, в конкретном случае 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 sealed класс.

Это работает, потому что T: StringOrInt означает наличие неявного параметра типа StringOrInt[T], а потому, что Scala просматривает объекты-компаньоны типа, чтобы увидеть, есть ли там импликации, чтобы код запрашивал работу этого типа.

  • 14
    Если class StringOrInt[T] сделан sealed , то упомянутая вами «утечка» («Конечно, это может быть StringOrInt[Boolean] клиентским кодом путем создания StringOrInt[Boolean] »), по крайней мере, если StringOrInt находится в собственный файл. Затем объекты-свидетели должны быть определены в том же источнике, что и StringOrInt .
  • 3
    Я попытался обобщить это решение несколько (опубликовано как ответ ниже). Основной недостаток по сравнению с подходом Either заключается в том, что мы теряем большую поддержку компилятора для проверки соответствия.
Показать ещё 7 комментариев
166

Майлз Сабин описывает очень хороший способ получить тип объединения в своем недавнем сообщении в блоге Unboxed union types в Scala через изоморфизм Карри-Говарда:

Сначала он определяет отрицание типов как

type ¬[A] = A => Nothing

используя закон Де Моргана, это позволяет ему определять типы объединения

type ∨[T, U] = ¬[¬[T] with ¬[U]]

Со следующими вспомогательными конструкциями

type ¬¬[A] = ¬[¬[A]]
type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }

вы можете написать типы объединения следующим образом:

def size[T : (Int |∨| String)#λ](t : T) = t match {
    case i : Int => i
    case s : String => s.length
}
  • 0
    Спасибо, что передали это. Абсолютно блестящий!
  • 11
    Это одна из самых удивительных вещей, которые я видел.
Показать ещё 11 комментариев
38

Dotty, новый экспериментальный компилятор Scala, поддерживает типы объединения (записано A | B), поэтому вы можете делать именно то, что вы хотели:

def foo(xs: (String | Int)*) = xs foreach {
   case _: String => println("str")
   case _: Int => println("int")
}
  • 0
    Один из этих дней.
  • 3
    Кстати, Dotty станет новой Scala 3 (об этом было объявлено несколько месяцев назад).
29

Вот путь Rex Kerr для кодирования типов объединения. Прямо и просто!

scala> def f[A](a: A)(implicit ev: (Int with String) <:< A) = a match {
     |   case i: Int => i + 1
     |   case s: String => s.length
     | }
f: [A](a: A)(implicit ev: <:<[Int with String,A])Int

scala> f(3)
res0: Int = 4

scala> f("hello")
res1: Int = 5

scala> f(9.2)
<console>:9: error: Cannot prove that Int with String <:< Double.
       f(9.2)
        ^

Источник: Комментарий # 27 под этот отличный комментарий в блоге от Miles Sabin, который предоставляет другой способ кодирования типов объединения в Scala.

  • 6
    К сожалению, эта кодировка может быть побеждена: scala> f(9.2: AnyVal) проходит проверку типов.
  • 0
    @Kipton: это грустно. Кодирование Майлза Сабина также страдает от этой проблемы?
Показать ещё 4 комментария
14

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

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.

Update:

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

13

Я как бы наткнулся на относительно чистую реализацию n-арных типов объединения, объединив понятие списков типов с упрощением Miles Sabin в этой области, который кто-то упоминает в другом ответе.

Данный тип ¬[-A], который контравариантен на A, по определению, данному A <: B, мы можем написать ¬[B] <: ¬[A], инвертируя порядок типов.

Приведенные типы A, B и X, мы хотим выразить X <: A || X <: B. Применяя контравариантность, получим ¬[A] <: ¬[X] || ¬[B] <: ¬[X]. Это может в свою очередь выражается как ¬[A] with ¬[B] <: ¬[X], в котором один из A или B должен быть супертипом самого X или X (подумайте о аргументах функции).

object Union {
  import scala.language.higherKinds

  sealed trait ¬[-A]

  sealed trait TSet {
    type Compound[A]
    type Map[F[_]] <: TSet
  }

  sealed trait ∅ extends TSet {
    type Compound[A] = A
    type Map[F[_]] = ∅ 
  }

  // Note that this type is left-associative for the sake of concision.
  sealed trait ∨[T <: TSet, H] extends TSet {
    // Given a type of the form `∅ ∨ A ∨ B ∨ ...` and parameter `X`, we want to produce the type
    // `¬[A] with ¬[B] with ... <:< ¬[X]`.
    type Member[X] = T#Map[¬]#Compound[¬[H]] <:< ¬[X]

    // This could be generalized as a fold, but for concision we leave it as is.
    type Compound[A] = T#Compound[H with A]

    type Map[F[_]] = T#Map[F] ∨ F[H]
  }

  def foo[A : (∅ ∨ String ∨ Int ∨ List[Int])#Member](a: A): String = a match {
    case s: String => "String"
    case i: Int => "Int"
    case l: List[_] => "List[Int]"
  }

  foo(42)
  foo("bar")
  foo(List(1, 2, 3))
  foo(42d) // error
  foo[Any](???) // error
}

Я потратил некоторое время, пытаясь объединить эту идею с верхней границей типов членов, как показано в TList harrah/up, однако реализация Map с ограничениями типов до сих пор оказалось сложным.

  • 0
    Это великолепно, спасибо! Я попробовал более ранние подходы, но продолжал иметь проблемы, используя это с родовыми типами как часть объединения. Это была единственная реализация, с которой я мог работать с универсальными типами.
  • 0
    К сожалению, но, вероятно, следовало ожидать, когда я пытаюсь использовать метод Scala, который берет тип объединения из кода Java, он не работает. Ошибка: (40, 29) java: метод setValue в классе Config не может быть применен к данным типам; требуется: X, scala.Predef. $ меньше $ двоеточие $ меньше <UnionTypes.package. $ u00AC <java.lang.Object>, UnionTypes.package. $ u00AC <X >> найдено: java.lang.String причина: не может быть выведена тип-переменная (и) X (фактические и формальные списки аргументов различаются по длине)
Показать ещё 4 комментария
12

Решение типа класса, вероятно, самый приятный способ перейти сюда, используя implicits. Это похоже на моноидный подход, упомянутый в книге Odersky/Spoon/Venners:

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)

              ^
  • 0
    Я могу ошибаться, но я не думаю, что это то, что искал ОП. OP спрашивал о типе данных, который мог бы представлять собой несвязанное объединение типов, а затем анализировать его во время выполнения, чтобы увидеть, каким оказался фактический тип. Классы типов не решат эту проблему, так как они являются конструкцией времени компиляции.
  • 4
    Реальный вопрос, который задавался, состоял в том, как выставить разное поведение для разных типов, но без перегрузки. Без знания классов типов (и, возможно, некоторого ознакомления с C / C ++), объединение типов представляется единственным решением. Существующий в Scala тип Either имеет тенденцию укреплять это убеждение. Использование классов типов через импликации Scala - лучшее решение основной проблемы, но это относительно новая концепция, которая до сих пор не получила широкого распространения, поэтому ФП даже не знал, что рассматривать их как возможную альтернативу объединенному типу.
Показать ещё 1 комментарий
8

Wed, как оператор типа Or[U,V], который может использоваться для ограничения параметров типа X таким образом, что либо X <: U, либо X <: V. Здесь определение, которое происходит так близко, как мы можем получить:

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

Вот как он используется:

// use

class A; class B extends A; class C extends B

def foo[X : (B Or String)#pf] = {}

foo[B]      // OK
foo[C]      // OK
foo[String] // OK
foo[A]      // ERROR!
foo[Number] // ERROR!

Используется несколько трюков типа Scala. Основным является использование обобщенных ограничений типа. При заданных типах U и V компилятор Scala предоставляет класс под названием U <:< V (и неявный объект этого класса) тогда и только тогда, когда компилятор Scala может доказать, что U является подтипом V. Heres более простой пример с использованием ограничений обобщенного типа, который работает в некоторых случаях:

def foo[X](implicit ev : (B with String) <:< X) = {}

Этот пример работает, когда X экземпляр класса B, a String или имеет тип, который не является ни супертипом, ни подтипом B или String. В первых двух случаях это истинно по определению ключевого слова with, что (B with String) <: B и (B with String) <: String, поэтому Scala предоставит неявный объект, который будет передан как ev: компилятор Scala будет правильно принимать foo[B] и foo[String].

В последнем случае Im полагается на то, что если U with V <: X, то U <: X или V <: X. Это кажется интуитивно верным, и я просто предполагаю это. Из этого предположения ясно, почему этот простой пример терпит неудачу, когда X является супертипом или подтипом либо B, либо String: например, в приведенном выше примере, foo[A] неправильно принят и foo[C] отклоняется неправильно, Опять же, нам нужно какое-то выражение типа для переменных U, V и X, которые истинны именно тогда, когда X <: U или X <: V.

Scala Здесь может помочь понятие контравариантности. Помните черту trait Inv[-X]? Потому что он контравариантен в своем параметре типа X, Inv[X] <: Inv[Y] тогда и только тогда, когда Y <: X. Это означает, что мы можем заменить приведенный выше пример на тот, который действительно будет работать:

trait Inv[-X]
def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {}

Это потому, что выражение (Inv[U] with Inv[V]) <: Inv[X] истинно по тому же предположению выше, точно когда Inv[U] <: Inv[X] или Inv[V] <: Inv[X], а по определению контравариантности это верно именно тогда, когда X <: U или X <: V.

Его можно сделать немного более многоразовым, объявив параметризуемый тип BOrString[X] и используя его следующим образом:

trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}

Scala теперь попытается построить тип BOrString[X] для каждого X, с которым вызывается foo, и тип будет построен именно тогда, когда X является подтипом либо B, либо String. Это работает, и есть сокращенная нотация. Синтаксис ниже эквивалентен (за исключением того, что ev теперь должен ссылаться в теле метода как implicitly[BOrString[X]], а не просто ev) и использует BOrString как связанный с текстом:

def foo[X : BOrString] = {}

То, что на самом деле похоже, является гибким способом создания привязки к типу типа. Контекст типа должен быть параметризуемым типом, и нам нужен параметризуемый способ его создания. Похоже, что они пытались выполнять функции по типам, так же, как мы выполняем функции по значениям. Другими словами, wed как-то вроде следующего:

type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X]

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

trait Inv[-X]
type Or[U,T] = {
    type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}

Здесь мы используем структурную типизацию и Scala s оператор фунта для создайте структурный тип Or[U,T], который, как гарантируется, имеет один внутренний тип. Это странный зверь. Чтобы дать некоторый контекст, функция def bar[X <: { type Y = Int }](x : X) = {} должна быть вызвана с подклассами AnyRef, которые имеют тип Y, определенный в них:

bar(new AnyRef{ type Y = Int }) // works!

Использование фунтового оператора позволяет нам ссылаться на внутренний тип Or[B, String]#pf и использовать инфиксную нотацию для оператора типа Or, мы приходим к нашему первоначальному определению foo:

def foo[X : (B Or String)#pf] = {}

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

type Or[U,T] = {
    type pf[X] = ((U => _) with (T => _)) <:< (X => _)
} 
8

Существует также этот хак:

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

  • 0
    См. Stackoverflow.com/questions/3422336/… . На самом деле есть более легкий взлом: просто добавьте (implicit e: DummyImplicit) к одной из сигнатур типа.
7

Вы можете взглянуть на MetaScala, который имеет что-то, называемое OneOf. У меня создается впечатление, что это не работает с операторами match, но вы можете имитировать соответствие с использованием функций более высокого порядка. Посмотрите этот фрагмент, но обратите внимание, что часть "смоделированного соответствия" закомментирована, может быть, потому, что она не совсем работает пока.

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

6

Я думаю, что первый класс непересекающийся тип является запечатанным супертипом, с альтернативными подтипами и неявными преобразованиями в/из желаемых типов дизъюнкции к этим альтернативным подтипам.

Я принимаю эти адреса комментарии 33 - 36 решения Miles Sabin, поэтому тип первого класса, который можно использовать на сайте использования, но я не тестировал его.

sealed trait IntOrString
case class IntOfIntOrString( v:Int ) extends IntOrString
case class StringOfIntOrString( v:String ) extends IntOrString
implicit def IntToIntOfIntOrString( v:Int ) = new IntOfIntOrString(v)
implicit def StringToStringOfIntOrString( v:String ) = new StringOfIntOrString(v)

object Int {
   def unapply( t : IntOrString ) : Option[Int] = t match {
      case v : IntOfIntOrString => Some( v.v )
      case _ => None
   }
}

object String {
   def unapply( t : IntOrString ) : Option[String] = t match {
      case v : StringOfIntOrString => Some( v.v )
      case _ => None
   }
}

def size( t : IntOrString ) = t match {
    case Int(i) => i
    case String(s) => s.length
}

scala> size("test")
res0: Int = 4
scala> size(2)
res1: Int = 2

Одна проблема Scala не будет использоваться в случае соответствия контексту, неявное преобразование из IntOfIntOrString в IntStringOfIntOrString в String), поэтому необходимо определить экстракторы и использовать case Int(i) вместо case i : Int.


ADD: Я ответил Майлзу Сабину в своем блоге следующим образом. Возможно, есть несколько улучшений над Либо:

  • Он распространяется на более чем 2 типа без каких-либо дополнительных помех на сайте использования или определения.
  • Аргументы помещаются в ящик неявно, например. не нужно size(Left(2)) или size(Right("test")).
  • Синтаксис совпадения шаблонов неявно распаковывается.
  • Бокс и распаковка могут быть оптимизированы точкой доступа JVM.
  • Синтаксис может быть таким, который был принят будущим типом объединения первого класса, поэтому миграция может быть плавной? Возможно, для имени типа объединения было бы лучше использовать V вместо Or, например. IntVString, `Int |v| String`, `Int or String` или мой любимый `Int|String`?

ОБНОВЛЕНИЕ: логическое отрицание дизъюнкции для вышеуказанного шаблона следует, и я добавил альтернативный (и, вероятно, более полезный) шаблон в блоге Miles Sabin.

sealed trait `Int or String`
sealed trait `not an Int or String`
sealed trait `Int|String`[T,E]
case class `IntOf(Int|String)`( v:Int ) extends `Int|String`[Int,`Int or String`]
case class `StringOf(Int|String)`( v:String ) extends `Int|String`[String,`Int or String`]
case class `NotAn(Int|String)`[T]( v:T ) extends `Int|String`[T,`not an Int or String`]
implicit def `IntTo(IntOf(Int|String))`( v:Int ) = new `IntOf(Int|String)`(v)
implicit def `StringTo(StringOf(Int|String))`( v:String ) = new `StringOf(Int|String)`(v)
implicit def `AnyTo(NotAn(Int|String))`[T]( v:T ) = new `NotAn(Int|String)`[T](v)
def disjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `Int or String`) = x
def negationOfDisjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `not an Int or String`) = x

scala> disjunction(5)
res0: Int|String[Int,Int or String] = IntOf(Int|String)(5)

scala> disjunction("")
res1: Int|String[String,Int or String] = StringOf(Int|String)()

scala> disjunction(5.0)
error: could not find implicit value for parameter ev: =:=[not an Int or String,Int or String]
       disjunction(5.0)
                  ^

scala> negationOfDisjunction(5)
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
       negationOfDisjunction(5)
                            ^

scala> negationOfDisjunction("")
error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String]
       negationOfDisjunction("")
                            ^
scala> negationOfDisjunction(5.0)
res5: Int|String[Double,not an Int or String] = NotAn(Int|String)(5.0)

ДРУГОЕ ОБНОВЛЕНИЕ: Что касается комментариев 23 и 35 Mile Sabin solution, вот способ объявить тип объединения на используемом сайте. Обратите внимание: он не распаковывается после первого уровня, т.е. Имеет преимущество расширяемый для любого числа типов в дизъюнкции, тогда как Either требуется вложенный бокс и парадигма в моем предыдущем комментарии 41 не были расширяемы. Другими словами, a D[Int ∨ String] можно присваивать (т.е. Является подтипом) a D[Int ∨ String ∨ Double].

type ¬[A] = (() => A) => A
type ∨[T, U] = ¬[T] with ¬[U]
class D[-A](v: A) {
  def get[T](f: (() => T)) = v match {
    case x : ¬[T] => x(f)
  }
}
def size(t: D[Int ∨ String]) = t match {
  case x: D[¬[Int]] => x.get( () => 0 )
  case x: D[¬[String]] => x.get( () => "" )
  case x: D[¬[Double]] => x.get( () => 0.0 )
}
implicit def neg[A](x: A) = new D[¬[A]]( (f: (() => A)) => x )

scala> size(5)
res0: Any = 5

scala> size("")
error: type mismatch;
 found   : java.lang.String("")
 required: D[?[Int,String]]
       size("")
            ^

scala> size("hi" : D[¬[String]])
res2: Any = hi

scala> size(5.0 : D[¬[Double]])
error: type mismatch;
 found   : D[(() => Double) => Double]
 required: D[?[Int,String]]
       size(5.0 : D[?[Double]])
                ^

По-видимому, компилятор Scala имеет три ошибки.

  • Он не будет выбирать правильную неявную функцию для любого типа после первого типа в дизъюнкции назначения.
  • Это не исключает случай D[¬[Double]] из соответствия.

3.

scala> class D[-A](v: A) {
  def get[T](f: (() => T))(implicit e: A <:< ¬[T]) = v match {
    case x : ¬[T] => x(f)
  }
}
error: contravariant type A occurs in covariant position in
       type <:<[A,(() => T) => T] of value e
         def get[T](f: (() => T))(implicit e: A <:< ?[T]) = v match {
                                           ^

Метод get не привязан должным образом к типу ввода, потому что компилятор не допустит A в ковариантной позиции. Можно утверждать, что это ошибка, потому что все, что мы хотим, является доказательством, мы никогда не имеем доступа к доказательствам в функции. И я сделал выбор не тестировать case _ в методе get, поэтому мне не пришлось бы распаковывать Option в match в size().


5 марта 2012 г. Предыдущее обновление нуждается в улучшении. Решение Miles Sabin корректно работало с подтипированием.

type ¬[A] = A => Nothing
type ∨[T, U] = ¬[T] with ¬[U]
class Super
class Sub extends Super

scala> implicitly[(Super ∨ String) <:< ¬[Super]]
res0: <:<[?[Super,String],(Super) => Nothing] = 

scala> implicitly[(Super ∨ String) <:< ¬[Sub]]
res2: <:<[?[Super,String],(Sub) => Nothing] = 

scala> implicitly[(Super ∨ String) <:< ¬[Any]]
error: could not find implicit value for parameter
       e: <:<[?[Super,String],(Any) => Nothing]
       implicitly[(Super ? String) <:< ?[Any]]
                 ^

Мое предварительное предложение по обновлению (для типа первого типа объединения) сломало подтипирование.

 scala> implicitly[D[¬[Sub]] <:< D[(Super ∨ String)]]
error: could not find implicit value for parameter
       e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]]
       implicitly[D[?[Sub]] <:< D[(Super ? String)]]
                 ^

Проблема заключается в том, что A в (() => A) => A появляется как в ковариантном (возвращаемом типе), так и в контравариантном (ввод функции или в этом случае возвращаемое значение функции, которая является входом функции), поэтому замены могут инвариантна.

Обратите внимание, что A => Nothing необходимо только потому, что мы хотим A в контравариантном положении, так что супертипы A не являются подтипами of D[¬[A]] и D[¬[A] with ¬[U]] (см. также). Поскольку нам нужна только двойная контравариантность, мы можем достичь эквивалента решения Miles, даже если мы можем отбросить ¬ и .

trait D[-A]

scala> implicitly[D[D[Super]] <:< D[D[Super] with D[String]]]
res0: <:<[D[D[Super]],D[D[Super] with D[String]]] = 

scala> implicitly[D[D[Sub]] <:< D[D[Super] with D[String]]]
res1: <:<[D[D[Sub]],D[D[Super] with D[String]]] = 

scala> implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
error: could not find implicit value for parameter
       e: <:<[D[D[Any]],D[D[Super] with D[String]]]
       implicitly[D[D[Any]] <:< D[D[Super] with D[String]]]
                 ^

Итак, полное исправление.

class D[-A] (v: A) {
  def get[T <: A] = v match {
    case x: T => x
  }
}

implicit def neg[A](x: A) = new D[D[A]]( new D[A](x) )

def size(t: D[D[Int] with D[String]]) = t match {
  case x: D[D[Int]] => x.get[D[Int]].get[Int]
  case x: D[D[String]] => x.get[D[String]].get[String]
  case x: D[D[Double]] => x.get[D[Double]].get[Double]
}

Обратите внимание, что предыдущие 2 ошибки в Scala остаются, но третий исключается, поскольку T теперь ограничен подтипом A.

Мы можем подтвердить, что работает подтипирование.

def size(t: D[D[Super] with D[String]]) = t match {
  case x: D[D[Super]] => x.get[D[Super]].get[Super]
  case x: D[D[String]] => x.get[D[String]].get[String]
}

scala> size( new Super )
res7: Any = Super@1272e52

scala> size( new Sub )
res8: Any = Sub@1d941d7

Я думал, что первоклассные типы пересечений очень важны, как для причины, по которым у Ceylon есть их, а потому, что вместо с учетом до Any, что означает, что unboxing с match на ожидаемых типах может генерировать ошибку времени выполнения, распаковка (разнородная коллекция, содержащая a) дизъюнкция может быть проверена по типу (Scala должен исправить отмеченные мной ошибки). Союзы более просты, чем сложность использованияэкспериментальный HList metascala для гетерогенных коллекций.

Показать ещё 2 комментария
4

Есть еще один способ, который немного легче понять, если вы не забудете Карри-Говарда:

type v[A,B] = Either[Option[A], Option[B]]

private def L[A,B](a: A): v[A,B] = Left(Some(a))
private def R[A,B](b: B): v[A,B] = Right(Some(b))  
// TODO: for more use scala macro to generate this for up to 22 types?
implicit def a2[A,B](a: A): v[A,B] = L(a)
implicit def b2[A,B](b: B): v[A,B] = R(b)
implicit def a3[A,B,C](a: A): v[v[A,B],C] = L(a2(a))
implicit def b3[A,B,C](b: B): v[v[A,B],C] = L(b2(b))
implicit def a4[A,B,C,D](a: A): v[v[v[A,B],C],D] = L(a3(a))
implicit def b4[A,B,C,D](b: B): v[v[v[A,B],C],D] = L(b3(b))    
implicit def a5[A,B,C,D,E](a: A): v[v[v[v[A,B],C],D],E] = L(a4(a))
implicit def b5[A,B,C,D,E](b: B): v[v[v[v[A,B],C],D],E] = L(b4(b))

type JsonPrimtives = (String v Int v Double)
type ValidJsonPrimitive[A] = A => JsonPrimtives

def test[A : ValidJsonPrimitive](x: A): A = x 

test("hi")
test(9)
// test(true)   // does not compile

Я использую аналогичную технику в дижоне

  • 0
    Может ли это работать с подтипами? Мое чувство кишки: нет, но я могу ошибаться. stackoverflow.com/questions/45255270/...
1

Добавление к уже отличным ответам здесь. Здесь суть, которая строится на типах союзов Майлза Сабина (и идей Джоша), но также делает их рекурсивно определенными, поэтому вы можете иметь > 2 типа в объединении (def foo[A : UNil Or Int Or String Or List[String])

https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb

NB: Я должен добавить, что после игры с вышеописанным для проекта я закончил возврат к типам простой старой суммы (т.е. запечатанной чертой с подклассами). Miles Sabin типы соединений отлично подходят для ограничения параметра типа, но если вам нужно вернуть тип объединения, то он не предлагает много.

  • 0
    Может ли это решить проблему подтипов A|C <: A|B|C ? stackoverflow.com/questions/45255270/… Я чувствую себя НЕТ, потому что тогда это будет означать, что A or C должны быть подтипом (A or B) or C но которые не содержат тип A or C поэтому нет надеюсь сделать A or C подтипом A or B or C с этой кодировкой хотя бы ... что вы думаете?
1

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

  • 5
    Хотя я уже некоторое время пользуюсь Scala, я не настолько осведомлен и не настолько умен, как вам кажется. В этом примере я вижу, как библиотека может предоставить решение. Тогда имеет смысл задаться вопросом, существует ли такая библиотека (или какая-то альтернатива).
0

Из документы с добавлением sealed:

sealed class Expr
case class Var   (x: String)          extends Expr
case class Apply (f: Expr, e: Expr)   extends Expr
case class Lambda(x: String, e: Expr) extends Expr

Относительно части sealed:

Можно определить дополнительные классы case, которые расширяют тип Expr в других частях программы (...). Эту форму расширяемости можно исключить, объявив базовый класс Expr запечатанным; в этом случае все классы, которые непосредственно расширяют Expr, должны быть в том же исходном файле, что и Expr.

  • 0
    Я не уверен, что это профсоюзные типы.
  • 0
    Это непересекающиеся типы объединений.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню