Uma maneira sugerida para lidar com definições duplas de métodos sobrecarregados é substituir a sobrecarga pela correspondência de padrões:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
Essa abordagem exige que entregemos a verificação de tipo estático nos argumentos para foo
. Seria muito melhor poder escrever
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
Posso me aproximar Either
, mas fica feio rapidamente com mais de dois tipos:
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")
}
}
Parece uma solução geral (elegante, eficiente) exigiria definir Either3
, Either4
, .... Alguém sabe de uma solução alternativa para atingir o mesmo fim? Que eu saiba, Scala não possui "disjunção de tipo" embutida. Além disso, as conversões implícitas definidas acima estão à espreita na biblioteca padrão em algum lugar para que eu possa importá-las?
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
.Either
abordagem parece ser que perdemos muito suporte do compilador para verificar a correspondência.trait StringOrInt ...
StringOrInt[T]
paraStringOrInt[-T]
(ver stackoverflow.com/questions/24387701/... )Miles Sabin descreve uma maneira muito legal de obter o tipo de união em sua recente postagem no blog Tipos de união sem caixa em Scala através do isomorfismo Curry-Howard :
Ele primeiro define negação de tipos como
usando a lei de De Morgan, isso permite que ele defina tipos de sindicatos
Com as seguintes construções auxiliares
você pode escrever tipos de união da seguinte maneira:
fonte
Dotty , um novo compilador Scala experimental, suporta tipos de união (escritos
A | B
), para que você possa fazer exatamente o que deseja:fonte
Aqui está a maneira de Rex Kerr de codificar tipos de união. Direto e simples!
Fonte: Comentário nº 27 deste excelente post de Miles Sabin, que fornece outra maneira de codificar tipos de união no Scala.
fonte
scala> f(9.2: AnyVal)
passa no verificador de letras.trait Contra[-A] {}
no lugar de todas as funções para nada. Então você obtém coisas comotype Union[A,B] = { type Check[Z] = Contra[Contra[Z]] <:< Contra[Contra[A] with Contra[B]] }
usadas comodef f[T: Union[Int, String]#Check](t: T) = t match { case i: Int => i; case s: String => s.length }
(sem unicode chique).É possível generalizar a solução de Daniel da seguinte maneira:
As principais desvantagens dessa abordagem são
Either
abordagem, ainda generalização exigiria a definição análogaOr3
,Or4
etc. traços. Obviamente, definir essas características seria muito mais simples do que definir asEither
classes correspondentes .Atualizar:
Mitch Blevins demonstra uma abordagem muito semelhante e mostra como generalizá-la para mais de dois tipos, apelidando-a de "gagueira ou".
fonte
Eu meio que me deparei com uma implementação relativamente limpa de tipos de união n-ária, combinando a noção de listas de tipos com uma simplificação do trabalho de Miles Sabin nessa área , que alguém menciona em outra resposta.
Dado tipo
¬[-A]
que é contrárioA
, por definição dadaA <: B
, podemos escrever¬[B] <: ¬[A]
, invertendo a ordem dos tipos.Tipos de dados
A
,B
eX
, queremos expressarX <: A || X <: B
. Aplicando contravariância, conseguimos¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. Este por sua vez pode ser expressa como¬[A] with ¬[B] <: ¬[X]
em que um dosA
ouB
deve ser um supertipoX
ouX
em si (pensar em argumentos de função).Passei algum tempo tentando combinar essa idéia com um limite superior nos tipos de membros, como visto nos
TList
s de harrah / up , no entanto, a implementação dosMap
limites com tipo até agora se mostrou desafiadora.fonte
Function1
como um tipo contravariante existente. Você não precisa de uma implementação, tudo que você precisa é evidência de conformidade (<:<
).Uma solução de classe de tipo é provavelmente o melhor caminho a percorrer aqui, usando implícitos. Isso é semelhante à abordagem monóide mencionada no livro Odersky / Spoon / Venners:
Se você executar isso no REPL:
fonte
Either
tipo preexistente de Scala tende a reforçar essa crença. Usar classes de tipo através dos implícitos de Scala é uma solução melhor para o problema subjacente, mas é um conceito relativamente novo e ainda não é amplamente conhecido, e é por isso que o OP nem sabia considerá-las como uma possível alternativa a um tipo de união.Gostaríamos de um operador de tipo
Or[U,V]
que possa ser usado para restringir os parâmetros de um tipo deX
maneira que sejaX <: U
ouX <: V
. Aqui está uma definição que chega o mais perto possível:Aqui está como é usado:
Isso usa alguns truques do tipo Scala. O principal é o uso de restrições de tipo generalizado . Dados os tipos
U
eV
, o compilador Scala fornece uma classe chamadaU <:< V
(e um objeto implícito dessa classe) se e somente se o compilador Scala puder provar que esseU
é um subtipo deV
. Aqui está um exemplo mais simples usando restrições de tipo generalizadas que funcionam em alguns casos:Este exemplo funciona quando
X
uma instância da classeB
, aString
, ou tem um tipo que não é um supertipo nem um subtipo deB
ouString
. Nos dois primeiros casos, é verdade pela definição dawith
palavra - chave que(B with String) <: B
e(B with String) <: String
, portanto, o Scala fornecerá um objeto implícito que será passado comoev
: o compilador do Scala aceitará corretamentefoo[B]
efoo[String]
.No último caso, estou confiando no fato de que se
U with V <: X
, entãoU <: X
ouV <: X
. Parece intuitivamente verdade, e estou simplesmente assumindo isso. Fica claro nessa suposição por que esse exemplo simples falha quandoX
é um supertipo ou subtipo de umB
ou outroString
: por exemplo, no exemplo acima,foo[A]
é aceito incorretamente efoo[C]
é rejeitado incorretamente. Mais uma vez, o que queremos é algum tipo de tipo de expressão sobre as variáveisU
,V
eX
isso é verdade exatamente quandoX <: U
ouX <: V
.A noção de contravariância de Scala pode ajudar aqui. Lembra da característica
trait Inv[-X]
? Porque é contravariante em seu parâmetro de tipoX
,Inv[X] <: Inv[Y]
se e somente seY <: X
. Isso significa que podemos substituir o exemplo acima por um que realmente funcione:Isso
(Inv[U] with Inv[V]) <: Inv[X]
ocorre porque a expressão é verdadeira, pela mesma suposição acima, exatamente quandoInv[U] <: Inv[X]
ouInv[V] <: Inv[X]
, e pela definição de contravariância, isso é verdade exatamente quandoX <: U
ouX <: V
.É possível tornar as coisas um pouco mais reutilizáveis, declarando um tipo parametrizável
BOrString[X]
e usando-o da seguinte maneira:Scala vai agora tentar construir o tipo
BOrString[X]
para cadaX
quefoo
é chamado com eo tipo será construído precisamente quandoX
é um subtipo de qualquerB
ouString
. Isso funciona e existe uma notação abreviada. A sintaxe abaixo é equivalente (exceto queev
agora deve ser referenciada no corpo do métodoimplicitly[BOrString[X]]
e não simplesmenteev
) e usadaBOrString
como um contexto de tipo ligado :O que realmente queremos é uma maneira flexível de criar um contexto de tipo vinculado. Um contexto de tipo deve ser um tipo parametrizável e queremos uma maneira parametrizável de criar um. Parece que estamos tentando organizar funções em tipos, assim como organizamos funções em valores. Em outras palavras, gostaríamos de algo como o seguinte:
Isso não é diretamente possível no Scala, mas há um truque que podemos usar para chegar bem perto. Isso nos leva à definição
Or
acima:Aqui usamos tipagem estrutural e o operador de libra de Scala para criar um tipo estrutural
Or[U,T]
que é garantido para ter um tipo interno. Este é um animal estranho. Para fornecer algum contexto, a funçãodef bar[X <: { type Y = Int }](x : X) = {}
deve ser chamada com subclassesAnyRef
que possuem um tipoY
definido nelas:O uso do operador pound nos permite fazer referência ao tipo interno
Or[B, String]#pf
e, usando a notação infix para o operador typeOr
, chegamos à nossa definição original defoo
:Podemos usar o fato de que os tipos de função são contrários ao primeiro parâmetro de tipo, a fim de evitar a definição da característica
Inv
:fonte
A|B <: A|B|C
problema? stackoverflow.com/questions/45255270/… não sei dizer.Há também este hack :
Consulte Como trabalhar em torno de ambiguidades de apagamento de tipo (Scala) .
fonte
(implicit e: DummyImplicit)
a uma das assinaturas de tipo.Você pode dar uma olhada no MetaScala , que tem algo chamado
OneOf
. Tenho a impressão de que isso não funciona bem commatch
instruções, mas que você pode simular a correspondência usando funções de ordem superior. Dê uma olhada neste trecho , por exemplo, mas observe que a parte "correspondência simulada" está comentada, talvez porque ainda não funcione.Agora, para alguns editorialistas: eu não acho que exista algo de estranho em definir Either3, Either4, etc., como você descreve. Isso é essencialmente duplo para os 22 tipos de tupla padrão incorporados ao Scala. Certamente seria bom se Scala tivesse tipos disjuntivos embutidos, e talvez alguma sintaxe agradável para eles
{x, y, z}
.fonte
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.
Um problema é que o Scala não empregará no contexto de correspondência de caso, uma conversão implícita de
IntOfIntOrString
paraInt
(eStringOfIntOrString
paraString
), portanto, deve definir extratores e usá-case Int(i)
los em vez decase i : Int
.ADD: Respondi a Miles Sabin em seu blog da seguinte maneira. Talvez haja várias melhorias em relação a:
size(Left(2))
ousize(Right("test"))
.V
vez deOr
, por exemploIntVString
, `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 .
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, aD[Int ∨ String]
é atribuível a (ou seja, é um subtipo de) aD[Int ∨ String ∨ Double]
.Aparentemente, o compilador Scala tem três erros.
D[¬[Double]]
caso da partida.3)
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 testarcase _
noget
método, para não ter que desmarcar umOption
nomatch
insize()
.5 de março de 2012: a atualização anterior precisa de uma melhoria. A solução de Miles Sabin funcionou corretamente com subtipagem.
A proposta da minha atualização anterior (para o tipo de união quase de primeira classe) quebrou a subtipagem.
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 queremosA
na posição contravariante, para que os supertipos deA
não sejam subtipos deD[¬[A]]
nemD[¬[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∨
.Portanto, a correção completa é.
Observe que os 2 erros anteriores no Scala permanecem, mas o terceiro é evitado, pois
T
agora está restrito ao subtipo deA
.Podemos confirmar os trabalhos de subtipagem.
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 commatch
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.fonte
size
função .size
não aceite maisD[Any]
como entrada.Existe outra maneira que é um pouco mais fácil de entender se você não gosta de Curry-Howard:
Eu uso técnica semelhante em dijon
fonte
Bem, isso é tudo muito inteligente, mas eu tenho certeza que você já sabe que as respostas para suas perguntas principais são várias variedades de "Não". O Scala lida com a sobrecarga de maneira diferente e, é preciso admitir, um pouco menos elegante do que você descreve. Parte disso é devido à interoperabilidade do Java, parte do fato de não querer atingir casos extremos do algoritmo de inferência de tipo, e parte do fato de simplesmente não ser Haskell.
fonte
Adicionando as ótimas respostas aqui. Aqui está uma essência que se baseia nos tipos de união de Miles Sabin (e nas idéias de Josh), mas também os define recursivamente, para que você possa ter> 2 tipos na união (
def foo[A : UNil Or Int Or String Or List[String]
)https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: Devo acrescentar que, depois de brincar com o exposto acima, acabei voltando aos tipos de soma antiga simples (ou seja, característica selada com subclasses). Os tipos de união Miles Sabin são ótimos para restringir o parâmetro type, mas se você precisar retornar um tipo de união, ele não oferecerá muito.
fonte
A|C <: A|B|C
problema de subtipagem? stackoverflow.com/questions/45255270/… Meu instinto não, porque então isso significaria queA or C
precisaria ser o subtipo de(A or B) or C
mas não contém o tipo,A or C
portanto não há esperança em criarA or C
um subtipoA or B or C
com essa codificação pelo menos .. . O que você acha ?Dos documentos , com a adição de
sealed
:Em relação à
sealed
peça:fonte