Я думаю, що роз'єднаний тип першого класу - це герметичний супертип, з альтернативними підтипами та неявними перетвореннями на / від бажаних типів диз'юнкції на ці альтернативні підтипи.
Я припускаю, що це стосується коментарів 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
.
ДОДАТИ: Я відповів Майлзу Сабіну на своєму блозі наступним чином. Можливо, є кілька вдосконалень над будь-яким:
- Він поширюється на більш ніж 2 типи, без додаткового шуму на сайті використання чи визначення.
- Аргументи затиснуті неявно, наприклад , не потрібні
size(Left(2))
або size(Right("test"))
.
- Синтаксис узгодження шаблону неявно без коробки.
- Бокс та розпакування можуть бути оптимізовані гарячою точкою JVM.
- Синтаксис може бути таким, який прийнятий майбутнім типом об'єднання першого класу, тому міграція може бути безпроблемною? Можливо, для назви типу об'єднання було б краще використовувати
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 до рішення Міле Сабіна , тут є спосіб оголосити тип об'єднання на сайті використання. Зауважимо, що вона не є коробкою після першого рівня, тобто вона має перевагу в тому, що вона може бути розширювана до будь-якої кількості типів диз'юнкції , тоді як 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()
.
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
очікуваними типами, може створити помилку виконання, розпакування ( гетерогенна колекція, що містить а) диз'юнкцію можна перевірити (Скала має виправити помилки, які я зазначив). Профспілки простіші , ніж складність використання експериментальної HList з metascala різнорідних колекцій.
class StringOrInt[T]
це зробленоsealed
, "витік", на який ви посилалися ("Звичайно, клієнтський код може створити сторону шляхом створенняStringOrInt[Boolean]
") підключається, принаймні, якщоStringOrInt
він знаходиться у власному файлі. Тоді предмети свідка повинні бути визначені в тому ж дусі, що іStringOrInt
.