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

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

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 ответов

Решение

Ну, в конкретном случае 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 Schulz ниже, вы не сделаете StringOrInt sealed учебный класс.

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

Майлз Сабин (Miles Sabin) описывает очень хороший способ получить тип объединения в своем недавнем сообщении в блоге " Unboxed Типы объединения" в 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
}

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

def foo(xs: (String | Int)*) = xs foreach {
   case _: String => println("str")
   case _: Int => println("int")
}

Вот способ 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 к этому прекрасному сообщению в блоге Майлза Сабина, в котором представлен еще один способ кодирования типов объединений в Scala.

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

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 классы.

Обновить:

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

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

Данный тип ¬[-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 харра / ап, однако реализация Map с границами типов до сих пор оказалось сложным.

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

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)

              ^

Мы хотели бы оператор типа 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, Вот более простой пример использования обобщенных ограничений типов, который работает в некоторых случаях:

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

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

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

Понятие контравариантности Скалы может помочь здесь. Запомнить черту 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] = {}

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

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

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

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

Здесь мы используем структурную типизацию и оператор фунта в Scala для создания структурного типа. 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 => _)
} 

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

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

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

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

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 в Int (а также StringOfIntOrString в String), поэтому необходимо определить экстракторы и использовать case Int(i) вместо case i : Int,


ДОБАВИТЬ: Я ответил Майлзу Сабину в его блоге следующим образом. Возможно, есть несколько улучшений по сравнению с Either:

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

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

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, вот способ объявить тип объединения на сайте использования. Обратите внимание, что он распакован после первого уровня, то есть имеет преимущество, заключающееся в том, что его можно расширить на любое количество типов в дизъюнкции, тогда как Either нуждается во вложенном боксе, и парадигма в моем предыдущем комментарии 41 не была расширяемой. Другими словами, D[Int ∨ String] присваивается (то есть является подтипом) 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 имеет три ошибки.

  1. Он не выберет правильную неявную функцию для любого типа после первого типа в дизъюнкции назначения.
  2. Это не исключает 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(),


05 марта 2012 г.: Предыдущее обновление нуждается в улучшении. Решение Майлза Сабина работало правильно с подтипами.

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 не являются подтипами D[¬[A]] ни D[¬[A] with ¬[U]] ( см. также). Поскольку нам нужна только двойная противоположность, мы можем получить эквивалентное решение Майлза, даже если мы можем отказаться от ¬ а также ,

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 остаются, но 3-й избегается как 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

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

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

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

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

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

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

В Scala 3 можно использовать типы Union. Запустить проект Scala 3: https://dotty.epfl.ch/#getting-started

Один из способов

sbt new lampepfl/dotty.g8

Затем вы можете изменить каталог на проект и ввести sbt console чтобы запустить REPL.

ссылка: https://dotty.epfl.ch/docs/reference/new-types/union-types.html

scala> def foo(xs: (Int | String)*) = xs foreach {
     |   case _: String => println("str")
     |   case _: Int => println("int")
     | }
def foo(xs: (Int | String)*): Unit

scala> foo(2,"2","acc",-223)                                                    
int
str
str
int

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

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

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

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

Из документов, с добавлением 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 часть:

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

Другие вопросы по тегам