O Scala não possui sistemas seguros enum
como o Java. Dado um conjunto de constantes relacionadas, qual seria a melhor maneira em Scala de representar essas constantes?
O Scala não possui sistemas seguros enum
como o Java. Dado um conjunto de constantes relacionadas, qual seria a melhor maneira em Scala de representar essas constantes?
Respostas:
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Exemplo de uso
object Main extends App {
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
import WeekDay._
def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
WeekDay.values filter isWorkingDay foreach println
}
Devo dizer que o exemplo copiado da documentação do Scala por skaffman acima é de utilidade limitada na prática (você também pode usar case object
s).
Para obter algo parecido com um Java Enum
(ou seja, com métodos toString
e valueOf
métodos sensíveis - talvez você esteja mantendo os valores de enum em um banco de dados), é necessário modificá-lo um pouco. Se você usou o código do skaffman :
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Considerando que usando a seguinte declaração:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
Você obtém resultados mais sensatos:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
valueOf
A substituição de @macias é withName
, que não retorna uma opção e gera um NSE se não houver correspondência. O que!
Map[Weekday.Weekday, Long]
e adicionar um valor, digamos Mon
que o compilador lança um erro de tipo inválido. Weekday.Weekday esperado Valor encontrado? Por que isso acontece?
Existem muitas maneiras de fazer.
1) Use símbolos. No entanto, ele não fornecerá nenhum tipo de segurança, além de não aceitar não-símbolos, onde um símbolo é esperado. Só estou mencionando aqui por exaustividade. Aqui está um exemplo de uso:
def update(what: Symbol, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case 'row => replaceRow(where, newValue)
case 'col | 'column => replaceCol(where, newValue)
case _ => throw new IllegalArgumentException
}
// At REPL:
scala> val a = unitMatrixInt(3)
a: teste7.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a('row, 1) = a.row(0)
res41: teste7.MatrixInt =
/ 1 0 0 \
| 1 0 0 |
\ 0 0 1 /
scala> a('column, 2) = a.row(0)
res42: teste7.MatrixInt =
/ 1 0 1 \
| 0 1 0 |
\ 0 0 0 /
2) Usando classe Enumeration
:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
ou, se você precisar serializar ou exibi-lo:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
Isso pode ser usado assim:
def update(what: Dimension, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case Row => replaceRow(where, newValue)
case Column => replaceCol(where, newValue)
}
// At REPL:
scala> a(Row, 2) = a.row(1)
<console>:13: error: not found: value Row
a(Row, 2) = a.row(1)
^
scala> a(Dimension.Row, 2) = a.row(1)
res1: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
scala> import Dimension._
import Dimension._
scala> a(Row, 2) = a.row(1)
res2: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
Infelizmente, isso não garante que todas as correspondências sejam contabilizadas. Se eu esquecesse de colocar Row ou Column na partida, o compilador Scala não teria me avisado. Isso me dá algum tipo de segurança, mas não tanto quanto se pode ganhar.
3) Objetos de caso:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
Agora, se eu deixar de fora um caso em a match
, o compilador me avisará:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
É usado praticamente da mesma maneira e nem precisa de import
:
scala> val a = unitMatrixInt(3)
a: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a(Row,2) = a.row(0)
res15: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 1 0 0 /
Você pode se perguntar, então, por que alguma vez usar uma Enumeração em vez de objetos de caso. Por uma questão de fato, os objetos de caso têm vantagens muitas vezes, como aqui. A classe Enumeration, no entanto, possui muitos métodos de coleção, como elementos (iterador no Scala 2.8), que retornam um iterador, mapa, flatMap, filtro etc.
Esta resposta é essencialmente uma parte selecionada deste artigo no meu blog.
Symbol
instâncias não podem ter espaços ou caracteres especiais. A maioria das pessoas quando se encontra com a Symbol
classe provavelmente pensa assim, mas na verdade está incorreta. Symbol("foo !% bar -* baz")
compila e funciona perfeitamente bem. Em outras palavras, você pode criar perfeitamente Symbol
instâncias envolvendo qualquer string (você simplesmente não pode fazê-lo com o açúcar sintático "coma único"). A única coisa que Symbol
garante é a singularidade de qualquer símbolo, tornando marginalmente mais rápido comparar e comparar.
String
, por exemplo, como argumento para um Symbol
parâmetro.
String
por outra classe que é basicamente um invólucro em torno de uma string e pode ser livremente convertida em ambas as direções (como é o caso Symbol
). Eu acho que foi isso que você quis dizer ao dizer "Não lhe dará nenhum tipo de segurança", mas não ficou muito claro, dado que o OP solicitou explicitamente soluções seguras para o tipo. Eu não tinha certeza se, no momento em que escrevi, você sabia que não só não é seguro quanto ao tipo, porque essas não são enumerações, mas também Symbol
nem garantem que o argumento passado não tenha caracteres especiais.
'foo
notação que faz obstam cadeias não identificadoras). Esse é um equívoco que eu queria dissipar para qualquer futuro leitor.
Uma maneira um pouco menos detalhada de declarar enumerações nomeadas:
object WeekDay extends Enumeration("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") {
type WeekDay = Value
val Sun, Mon, Tue, Wed, Thu, Fri, Sat = Value
}
WeekDay.valueOf("Wed") // returns Some(Wed)
WeekDay.Fri.toString // returns Fri
Obviamente, o problema aqui é que você precisará manter a ordem dos nomes e vals sincronizados, o que é mais fácil se os nomes e val forem declarados na mesma linha.
Você pode usar uma classe abstrata selada em vez da enumeração, por exemplo:
sealed abstract class Constraint(val name: String, val verifier: Int => Boolean)
case object NotTooBig extends Constraint("NotTooBig", (_ < 1000))
case object NonZero extends Constraint("NonZero", (_ != 0))
case class NotEquals(x: Int) extends Constraint("NotEquals " + x, (_ != x))
object Main {
def eval(ctrs: Seq[Constraint])(x: Int): Boolean =
(true /: ctrs){ case (accum, ctr) => accum && ctr.verifier(x) }
def main(args: Array[String]) {
val ctrs = NotTooBig :: NotEquals(5) :: Nil
val evaluate = eval(ctrs) _
println(evaluate(3000))
println(evaluate(3))
println(evaluate(5))
}
}
Depois de fazer uma extensa pesquisa sobre todas as opções em torno de "enumerações" no Scala, publiquei uma visão geral muito mais completa desse domínio em outro thread do StackOverflow . Ele inclui uma solução para o padrão "traço selado + objeto de caso" em que resolvi o problema de pedido de inicialização de classe / objeto da JVM.
Em Scala, é muito confortável com https://github.com/lloydmeta/enumeratum
Projeto é realmente bom com exemplos e documentação
Apenas este exemplo de seus documentos deve interessá-lo
import enumeratum._
sealed trait Greeting extends EnumEntry
object Greeting extends Enum[Greeting] {
/*
`findValues` is a protected method that invokes a macro to find all `Greeting` object declarations inside an `Enum`
You use it to implement the `val values` member
*/
val values = findValues
case object Hello extends Greeting
case object GoodBye extends Greeting
case object Hi extends Greeting
case object Bye extends Greeting
}
// Object Greeting has a `withName(name: String)` method
Greeting.withName("Hello")
// => res0: Greeting = Hello
Greeting.withName("Haro")
// => java.lang.IllegalArgumentException: Haro is not a member of Enum (Hello, GoodBye, Hi, Bye)
// A safer alternative would be to use `withNameOption(name: String)` method which returns an Option[Greeting]
Greeting.withNameOption("Hello")
// => res1: Option[Greeting] = Some(Hello)
Greeting.withNameOption("Haro")
// => res2: Option[Greeting] = None
// It is also possible to use strings case insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello
Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)
// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello
Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None
// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello
Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)