Existem diretrizes de boas práticas sobre quando usar classes de caso (ou objetos de caso) versus estender a enumeração no Scala?
Eles parecem oferecer alguns dos mesmos benefícios.
scala
enumeration
case-class
Alex Miller
fonte
fonte
enum
(para meados de 2020).Respostas:
Uma grande diferença é que eles
Enumeration
vêm com suporte para instancia-los a partir de algumaname
String. Por exemplo:Então você pode fazer:
Isso é útil quando se deseja persistir enumerações (por exemplo, em um banco de dados) ou criá-las a partir de dados residentes em arquivos. No entanto, acho que, em geral, as enumerações são um pouco desajeitadas no Scala e têm a sensação de um complemento estranho, então agora tenho a tendência de usar
case object
s. Acase object
é mais flexível que um enum:Então agora eu tenho a vantagem de ...
Como o @ chaotic3quilibrium apontou (com algumas correções para facilitar a leitura):
Para acompanhar as outras respostas aqui, as principais desvantagens de
case object
s sobreEnumeration
s são:Não é possível iterar em todas as instâncias da "enumeração" . Certamente é esse o caso, mas achei extremamente raro na prática que isso seja necessário.
Não é possível instanciar facilmente a partir do valor persistente . Isso também é verdade, mas, exceto no caso de grandes enumerações (por exemplo, todas as moedas), isso não apresenta uma sobrecarga enorme.
fonte
trade.ccy
no exemplo de característica selada.case
object
gera uma pegada de código maior (~ 4x) queEnumeration
? Distinção útil, especialmente parascala.js
projetos que necessitam de uma pegada reduzida.ATUALIZAÇÃO: Uma nova solução baseada em macro foi criada, que é muito superior à solução descrita abaixo. Eu recomendo fortemente o uso desta nova solução baseada em macro . E parece que os planos para o Dotty farão com que esse estilo de solução enum faça parte do idioma. Whoohoo!
Resumo:
Existem três padrões básicos para tentar reproduzir o Java
Enum
em um projeto Scala. Dois dos três padrões; diretamente usando JavaEnum
escala.Enumeration
, não são capazes de ativar a correspondência exaustiva de padrões do Scala. E o terceiro; "traço selado + objeto de caso", possui ... mas possui complicações de inicialização de classe / objeto da JVM, resultando em geração inconsistente de índice ordinal.Eu criei uma solução com duas classes; Enumeração e EnumerationDecorated , localizado nesta Gist . Não publiquei o código nesse segmento, pois o arquivo para Enumeração era bastante grande (+400 linhas - contém muitos comentários explicando o contexto de implementação).
Detalhes:
a pergunta que você está fazendo é bem geral; "... quando usar
case
classesobjects
versus extensão[scala.]Enumeration
". E acontece que existem MUITAS respostas possíveis, cada resposta dependendo das sutilezas dos requisitos específicos do projeto que você possui. A resposta pode ser reduzida para três padrões básicos.Para começar, vamos ter certeza de que estamos trabalhando com a mesma idéia básica do que é uma enumeração. Vamos definir uma enumeração principalmente em termos do
Enum
fornecido no Java 5 (1.5) :Enum
, seria bom poder aproveitar explicitamente a verificação exaustiva de correspondência de padrões do Scala para uma enumeraçãoA seguir, vejamos as versões resumidas dos três padrões de solução mais comuns publicados:
A) Na verdade, diretamente usando o padrão Java
Enum
(em um projeto Scala / Java misto):Os seguintes itens da definição de enumeração não estão disponíveis:
Para meus projetos atuais, não tenho o benefício de correr riscos ao longo do caminho misto do projeto Scala / Java. E mesmo que eu pudesse optar por fazer um projeto misto, o item 7 é essencial para permitir que eu pegue problemas de tempo de compilação se / quando adiciono / removo membros da enumeração ou estou escrevendo algum novo código para lidar com os membros existentes da enumeração.
B) Usando o padrão "
sealed trait
+case objects
":Os seguintes itens da definição de enumeração não estão disponíveis:
É discutível que ele realmente atenda aos itens de definição de enumeração 5 e 6. Para 5, é muito difícil afirmar que é eficiente. Para 6, não é realmente fácil estender para armazenar dados adicionais associados a singleton.
C) Usando o
scala.Enumeration
padrão (inspirado nesta resposta do StackOverflow ):Os seguintes itens da definição de enumeração não estão disponíveis (é idêntico à lista para usar diretamente o Java Enum):
Novamente para meus projetos atuais, o item 7 é fundamental para permitir que eu pegue problemas de tempo de compilação se / quando adiciono / removo membros da enumeração ou estou escrevendo algum novo código para lidar com os membros existentes da enumeração.
Portanto, dada a definição acima de uma enumeração, nenhuma das três soluções acima funciona, pois elas não fornecem tudo descrito na definição de enumeração acima:
Cada uma dessas soluções pode ser retrabalhada / expandida / refatorada para tentar cobrir alguns dos requisitos ausentes de cada um. No entanto, nem o Java
Enum
nem asscala.Enumeration
soluções podem ser suficientemente expandidas para fornecer o item 7. E para meus próprios projetos, esse é um dos valores mais convincentes do uso de um tipo fechado no Scala. Eu prefiro fortemente avisos / erros de tempo de compilação para indicar que tenho uma lacuna / problema no meu código, em vez de precisar coletá-lo de uma exceção / falha no tempo de execução da produção.Nesse sentido, comecei a trabalhar com o
case object
caminho para ver se conseguia produzir uma solução que cobrisse toda a definição de enumeração acima. O primeiro desafio foi avançar no núcleo do problema de inicialização de classe / objeto da JVM (abordado em detalhes nesta postagem do StackOverflow ). E finalmente consegui descobrir uma solução.Como minha solução são duas características; Enumeration and EnumerationDecorated , e como a
Enumeration
característica tem mais de 400 linhas (muitos comentários explicando o contexto), deixo de colar o texto nesse segmento (o que o faria esticar a página consideravelmente). Para detalhes, pule diretamente para o Gist .Eis como a solução acaba usando a mesma ideia de dados acima (versão totalmente comentada disponível aqui ) e implementada no
EnumerationDecorated
.Este é um exemplo de uso de um novo par de traços de enumeração que eu criei (localizado nesta lista ) para implementar todos os recursos desejados e descritos na definição de enumeração.
Uma preocupação expressa é que os nomes dos membros da enumeração devem ser repetidos (
decorationOrderedSet
no exemplo acima). Embora eu o minimizasse até uma única repetição, não conseguia ver como torná-lo ainda menos devido a dois problemas:getClass.getDeclaredClasses
tem uma ordem indefinida (e é improvável que esteja na mesma ordem que ascase object
declarações no código-fonte)Dadas essas duas questões, tive que desistir de tentar gerar uma ordem implícita e exigir explicitamente que o cliente a definisse e a declarasse com algum tipo de noção de conjunto ordenada. Como as coleções Scala não possuem uma implementação de conjunto ordenada por inserção, o melhor que pude fazer foi usar uma
List
verificação de tempo de execução e, em seguida, verificar se realmente era um conjunto. Não é assim que eu teria preferido ter conseguido isso.E dado o design exigido para a segunda ordem de lista / conjunto
val
, conforme oChessPiecesEnhancedDecorated
exemplo acima, foi possível adicionarcase object PAWN2 extends Member
e depois esquecer de adicionarDecoration(PAWN2,'P2', 2)
adecorationOrderedSet
. Portanto, há uma verificação de tempo de execução para verificar se a lista não é apenas um conjunto, mas contém TODOS os objetos de caso que estendem o arquivosealed trait Member
. Essa foi uma forma especial de reflexão / macro inferno para resolver.Por favor, deixe comentários e / ou feedback sobre o Gist .
fonte
org.scalaolio.util.Enumeration
eorg.scalaolio.util.EnumerationDecorated
: scalaolio.orgObjetos de caso já retornam seus nomes para seus métodos toString, portanto, passá-los separadamente é desnecessário. Aqui está uma versão semelhante à do jho (métodos de conveniência omitidos por questões de brevidade):
Objetos são preguiçosos; usando vals, podemos descartar a lista, mas precisamos repetir o nome:
Se você não se importa com trapaças, pode pré-carregar seus valores de enumeração usando a API de reflexão ou algo como o Google Reflections. Objetos de caso não preguiçosos oferecem a sintaxe mais limpa:
Agradável e limpo, com todas as vantagens de classes de casos e enumerações Java. Pessoalmente, defino os valores de enumeração fora do objeto para corresponder melhor ao código idiomático do Scala:
fonte
Currency.values
, só recebo de volta os valores acessados anteriormente. Existe alguma maneira de contornar isso?As vantagens de usar classes de caso sobre enumerações são:
As vantagens de usar enumerações em vez de classes de caso são:
Portanto, em geral, se você só precisa de uma lista de constantes simples por nome, use enumerações. Caso contrário, se você precisar de algo um pouco mais complexo ou desejar a segurança extra do compilador, informando se todas as correspondências foram especificadas, use classes de casos.
fonte
ATUALIZAÇÃO: O código abaixo possui um erro, descrito aqui . O programa de teste abaixo funciona, mas se você usasse o DayOfWeek.Mon (por exemplo) antes do DayOfWeek, ele falharia porque o DayOfWeek não foi inicializado (o uso de um objeto interno não faz com que um objeto externo seja inicializado). Você ainda pode usar esse código se fizer algo como
val enums = Seq( DayOfWeek )
na sua classe principal, forçando a inicialização de suas enumerações ou usar as modificações do caotic3quilibrium. Ansioso para um enum baseado em macro!Se você quiser
então o seguinte pode ser de seu interesse. Feedback bem-vindo.
Nesta implementação, existem classes básicas abstratas Enum e EnumVal, que você estende. Veremos essas classes em um minuto, mas primeiro, veja como você definiria uma enumeração:
Observe que você precisa usar cada valor de enum (chame seu método de aplicação) para dar vida a ele. [Eu gostaria que os objetos internos não fossem preguiçosos, a menos que eu pedisse especificamente que eles fossem. Eu acho que.]
É claro que poderíamos adicionar métodos / dados ao DayOfWeek, Val ou aos objetos de caso individuais, se assim o desejássemos.
E aqui está como você usaria essa enumeração:
Aqui está o que você obtém quando o compila:
Você pode substituir "correspondência do dia" por "correspondência do dia: @unchecked)" onde não deseja esses avisos ou simplesmente incluir um caso abrangente no final.
Quando você executa o programa acima, obtém esta saída:
Observe que, como a Lista e os Mapas são imutáveis, você pode remover facilmente elementos para criar subconjuntos, sem interromper a enumeração em si.
Aqui está a própria classe Enum (e EnumVal dentro dela):
E aqui está um uso mais avançado dele que controla os IDs e adiciona dados / métodos à abstração Val e à própria enumeração:
fonte
var
] é um pecado mortal limítrofe no mundo das PF" - não acho que essa opinião seja universalmente aceita.Eu tenho uma boa e simples lib aqui que permite que você use características / classes seladas como valores de enumeração sem precisar manter sua própria lista de valores. Ele se baseia em uma macro simples que não depende do buggy
knownDirectSubclasses
.https://github.com/lloydmeta/enumeratum
fonte
Atualização em março de 2017: conforme comentado por Anthony Accioly , o
scala.Enumeration/enum
PR foi fechado.Dotty (compilador de próxima geração para Scala) assumirá a liderança, embora a edição dotty de 1970 e o PR 1958 de Martin Odersky .
Nota: existe agora (agosto de 2016, mais de 6 anos depois) uma proposta para remover
scala.Enumeration
: PR 5352fonte
Outra desvantagem de classes de caso versus enumerações, quando você precisará iterar ou filtrar todas as instâncias. Esse é um recurso interno da Enumeração (e também das enumerações Java), enquanto as classes de caso não suportam automaticamente esse recurso.
Em outras palavras: "não há uma maneira fácil de obter uma lista do conjunto total de valores enumerados com classes de caso".
fonte
Se você é sério em manter a interoperabilidade com outras linguagens da JVM (por exemplo, Java), a melhor opção é escrever enumerações Java. Eles funcionam de forma transparente a partir dos códigos Scala e Java, o que é mais do que pode ser dito para
scala.Enumeration
objetos ou caso. Não vamos ter uma nova biblioteca de enumerações para cada novo projeto de hobby no GitHub, se puder ser evitado!fonte
Eu já vi várias versões de fazer uma classe de caso imitar uma enumeração. Aqui está a minha versão:
O que permite construir classes de caso que se parecem com o seguinte:
Talvez alguém possa inventar um truque melhor do que simplesmente adicionar uma classe de cada caso à lista, como eu fiz. Isso foi tudo o que pude apresentar na época.
fonte
Estive indo e voltando nessas duas opções nas últimas vezes em que precisei delas. Até recentemente, minha preferência era pela opção de característica / objeto de caso selado.
1) Declaração de Enumeração Scala
2) Traços selados + objetos de caso
Embora nenhum deles realmente atenda a tudo o que uma enumeração java oferece, abaixo estão os prós e contras:
Enumeração Scala
Prós: -Funções para instanciar com opção ou assumir diretamente precisão (mais fácil ao carregar de um armazenamento persistente) -Iteração sobre todos os valores possíveis
Contras: -O aviso de compilação para pesquisa não exaustiva não é suportado (torna a correspondência de padrões menos ideal)
Objetos de caso / traços selados
Prós: -Usando traços selados, podemos pré-instanciar alguns valores, enquanto outros podem ser injetados no momento da criação -suporte completo para correspondência de padrões (métodos de aplicação / não aplicação definidos)
Contras: -Instanciando de um armazenamento persistente - você geralmente precisa usar a correspondência de padrões aqui ou definir sua própria lista de todos os possíveis 'valores de enumeração'
O que finalmente me fez mudar de opinião foi algo como o seguinte trecho:
As
.get
chamadas eram hediondas - usando enumeração, em vez disso, posso simplesmente chamar o método withName na enumeração da seguinte maneira:Portanto, acho que minha preferência daqui para frente é usar Enumerações quando os valores tiverem a intenção de serem acessados de um repositório e objetos de caso / traços fechados, caso contrário.
fonte
Eu prefiro
case objects
(é uma questão de preferência pessoal). Para lidar com os problemas inerentes a essa abordagem (analisar a sequência e repetir todos os elementos), adicionei algumas linhas que não são perfeitas, mas são eficazes.Estou colando o código aqui, esperando que possa ser útil e também que outros possam aprimorá-lo.
fonte
Para aqueles que ainda desejam obter a resposta do GatesDa para o trabalho : você pode apenas fazer referência ao objeto de caso após declará-lo para instanciar:
fonte
Eu acho que a maior vantagem de ter
case classes
maisenumerations
é que você pode usar o padrão de classe de tipo, também conhecido como polimorfismo ad-hoc . Não precisa corresponder enumerações como:em vez disso, você terá algo como:
fonte