объединение типов в scala с подтипами: A|B ‹: A|B|C

Я хотел бы, чтобы тип A|B был подтипом A|B|C. Возможно ли это закодировать в Scala? Если да, то как?

Я надеялся, что смогу implicitly[¬¬[IF] <:< T] скомпилировать ниже (исходный код здесь), но это не так. Есть ли способ исправить этот код, чтобы разрешить подтипирование?

object NUnion{
  type ¬¬[A] = ¬[¬[A]]

  type ¬[A] = A => Nothing
  trait Disj[T] {
    type or[S] = Disj[T with ¬[S]]
    type apply = ¬[T]
  }

  // for convenience
  type disj[T] = { type or[S] = Disj[¬[T]]#or[S] }


  type T = disj[Int]#or[Float]#or[String]#apply
  type IF = disj[Int]#or[Float]#apply
  implicitly[¬¬[Int] <:< T] // works
  // implicitly[¬¬[Double] <:< T] // doesn't work
  // implicitly[¬¬[IF] <:< T] // doesn't work - but it should

}

Я также пробовал это (из здесь):

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

  f(1) //works
  f("bla") // works
  def g[R]()(implicit ev: (Int with String with Boolean) <:< R):R = "go" // does not work

}

но здесь я не могу сделать тип объединения «первоклассным», они могут существовать только как типы аргументов, а не как возвращаемые типы.

Та же проблема с этим подходом:

object Map{
  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]"
    }


    def geza[A : (∅ ∨ String ∨ Int ∨ List[Int])#Member] : A = "45" // does not work 


    foo(geza)

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

}

person jhegedus    schedule 22.07.2017    source источник
comment
связанные : contributors.scala-lang. org/t/dotty-style-union-types-in-scala/   -  person jhegedus    schedule 22.07.2017


Ответы (2)


Тип объединения Scala.js (источник и тесты) поддерживает A | B подтип A | B | C. Он даже поддерживает такие перестановки, как A | B подтип B | C | A.

person sjrd    schedule 22.07.2017
comment
Фантастика, я думаю, Scala не может поддерживать это на стороне сервера? Или я могу использовать их также на стороне сервера? Используют ли они что-то конкретное для Scala.js? Мне кажется, что это может работать и на стороне сервера, если немного повозиться... верно? - person jhegedus; 22.07.2017
comment
В основном он может работать на стороне сервера, но вам придется использовать абстрактный type |[A, B] вместо sealed trait, чтобы его стирание было Object и приведения к нему были успешными. У этого есть один недостаток, заключающийся в том, что нет сопутствующего объекта, в который могут быть помещены имплициты, чтобы они автоматически попадали в неявную область видимости. Это означает, что в тех местах, где вам нужно сохранить отношение подтипа, вам понадобится импорт. В то время как в Scala.js вам нужен импорт только тогда, когда вы записываете A | B. - person sjrd; 22.07.2017
comment
Спасибо, нужно посмотреть, как я могу заставить это работать, и подумать об этой разнице стирания между Scala.js и Scala. - person jhegedus; 23.07.2017
comment
Я нашел способ указать неявную область видимости для type | (без изменения его стирания): com/Jasper-M/96ca24e8d2aa6160159abe2c4f745206 - person Jasper-M; 23.07.2017

Ваш первый подход работает нормально для меня. Он также работает с перестановками типов.

type IFS = disj[Int]#or[Float]#or[String]#apply
type IF = disj[Int]#or[Float]#apply
type IS = disj[Int]#or[String]#apply
type IFD = disj[Int]#or[Float]#or[Double]#apply
type FI = disj[Float]#or[Int]#apply

scala> implicitly[IF <:< IFS]
res0: <:<[IF,IFS] = <function1>

scala> implicitly[IS <:< IFS]
res1: <:<[IS,IFS] = <function1>

scala> implicitly[FI <:< IFS]
res2: <:<[FI,IFS] = <function1>

scala> implicitly[IFD <:< IFS]
<console>:18: error: Cannot prove that IFD <:< IFS.
       implicitly[IFD <:< IFS]

Конечно, вы не должны поднимать IF в тип объединения с ¬¬[IF], потому что это уже тип объединения. Вам нужно сделать ¬¬[Int], только потому, что Int не является типом объединения в этом подходе.

person Kolmar    schedule 22.07.2017
comment
Интересно, спасибо! Могу ли я использовать IF в качестве возвращаемого типа в функции? - person jhegedus; 22.07.2017