Eu estou pensando que o tipo disjuntivo de primeira classe é um supertipo selado, com os subtipos alternativos e conversões implícitas de / para os tipos desejados da disjunção para esses subtipos alternativos.
Suponho que isso aborda os comentários 33 a 36 da solução de Miles Sabin, portanto, o tipo de primeira classe que pode ser empregado no site de uso, mas não o testei.
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
Um problema é que o Scala não empregará no contexto de correspondência de caso, uma conversão implícita de IntOfIntOrString
para Int
(e StringOfIntOrString
para String
), portanto, deve definir extratores e usá- case Int(i)
los em vez de case i : Int
.
ADD: Respondi a Miles Sabin em seu blog da seguinte maneira. Talvez haja várias melhorias em relação a:
- Ele se estende a mais de 2 tipos, sem nenhum ruído adicional no site de uso ou definição.
- Os argumentos estão em caixas implicitamente, por exemplo, não precisa
size(Left(2))
ou size(Right("test"))
.
- A sintaxe da correspondência de padrões é implicitamente desmarcada.
- O boxe e o unboxing podem ser otimizados pelo hotspot da JVM.
- A sintaxe pode ser a adotada por um futuro tipo de união de primeira classe; portanto, a migração talvez seja perfeita? Talvez para o nome do tipo de união, seria melhor usar em
V
vez de Or
, por exemplo IntVString
, ` Int |v| String
`, ` Int or String
` ou meu favorito ` Int|String
`?
ATUALIZAÇÃO: A negação lógica da disjunção para o padrão acima segue e eu adicionei um padrão alternativo (e provavelmente mais útil) no blog de 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)
OUTRA ATUALIZAÇÃO: Em relação aos comentários 23 e 35 da solução da Mile Sabin , aqui está uma maneira de declarar um tipo de união no site de uso. Observe que ele está fora da caixa após o primeiro nível, ou seja, tem a vantagem de ser extensível a qualquer número de tipos na disjunção , ao passo que Either
precisa de box aninhado e o paradigma no meu comentário anterior 41 não era extensível. Em outras palavras, a D[Int ∨ String]
é atribuível a (ou seja, é um subtipo de) 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]])
^
Aparentemente, o compilador Scala tem três erros.
- Ele não escolherá a função implícita correta para nenhum tipo após o primeiro tipo na disjunção de destino.
- Não exclui o
D[¬[Double]]
caso da partida.
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 {
^
O método get não é restrito corretamente no tipo de entrada, porque o compilador não permitirá A
na posição covariante. Pode-se argumentar que é um bug, porque tudo o que queremos é evidência, nunca acessamos a evidência na função. E fiz a escolha de não testar case _
no get
método, para não ter que desmarcar um Option
no match
in size()
.
5 de março de 2012: a atualização anterior precisa de uma melhoria. A solução de Miles Sabin funcionou corretamente com subtipagem.
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]]
^
A proposta da minha atualização anterior (para o tipo de união quase de primeira classe) quebrou a subtipagem.
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)]]
^
O problema é que A
em (() => A) => A
aparece em ambos os co-variante (tipo de retorno) e contravariante (entrada de função, ou, neste caso, um valor de retorno da função que é uma função de entrada) posições, assim substituições só pode ser invariante.
Observe que isso A => Nothing
é necessário apenas porque queremos A
na posição contravariante, para que os supertipos de A
não sejam subtipos de D[¬[A]]
nem D[¬[A] with ¬[U]]
( veja também ). Como precisamos apenas de contravariância dupla, podemos obter o equivalente à solução de Miles, mesmo que possamos descartar o ¬
e ∨
.
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]]]
^
Portanto, a correção completa é.
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]
}
Observe que os 2 erros anteriores no Scala permanecem, mas o terceiro é evitado, pois T
agora está restrito ao subtipo de A
.
Podemos confirmar os trabalhos de subtipagem.
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
Eu tenho pensado que os tipos de interseção de primeira classe são muito importantes, tanto pelas razões que o Ceilão os possui , como porque, em vez de se dedicar a Any
isso, unboxing com match
tipos esperados pode gerar um erro de tempo de execução, o unboxing de uma coleção heterogênea contendo a) a disjunção pode ser verificada do tipo (Scala precisa corrigir os bugs que observei). Os sindicatos são mais simples do que a complexidade do uso experimental hList de metascala para coleções heterogêneas.
class StringOrInt[T]
for feitosealed
, o "vazamento" ao qual você se referiu ("Claro, isso pode ser evitado pelo código do cliente criando umStringOrInt[Boolean]
") é conectado, pelo menos se eleStringOrInt
residir em um arquivo próprio. Então os objetos testemunha devem ser definidos na mesma fonte queStringOrInt
.