Tip-güvenli enum tipleri nasıl modellenir?


311

Scala, enumJava'nın yaptığı gibi güvenli değildir . Bir takım ilgili sabitler göz önüne alındığında, Scala'da bu sabitleri temsil etmenin en iyi yolu ne olurdu?


2
Neden sadece java enum kullanmıyorsunuz? Bu hala düz java kullanmayı tercih ettiğim birkaç şeyden biri.
Max

1
Scala Numaralandırma ve alternatifler hakkında küçük bir genel bakış yazdım, yararlı bulabilirsiniz: pedrorijo.com/blog/scala-enums/
pedrorijo91

Yanıtlar:


187

http://www.scala-lang.org/docu/files/api/scala/Enumeration.html

Örnek kullanım

  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
  }

2
Ciddi, Uygulama kullanılmamalıdır. Düzeltilmedi; Schildmeijer'in bahsettiği sorunları olmayan yeni bir sınıf olan App tanıtıldı. "Object foo App {...}" 'ı genişletir. Ve args değişkeni aracılığıyla komut satırı argümanlarına anında erişebilirsiniz.
AmigoNico

scala.Enumeration (yukarıdaki "Object WeekDay" kod örneğinizde kullandığınız şeydir) ayrıntılı desen eşleşmesi sunmaz. Şu anda Scala'da kullanılan tüm farklı numaralandırma modellerini araştırdım ve bu StackOverflow yanıtında (her iki scala.Enumeration ve "mühürlü özellik + vaka nesnesi" deseninin en iyi özelliklerini sunan yeni bir desen de dahil olmak üzere) onlara genel bir bakış verdim . com / a / 25923651/501113
chaotic3quilibrium

377

Skaffman tarafından Scala belgelerinden kopyalanan örneğin pratikte sınırlı faydası olduğunu söylemeliyim (siz de kullanabilirsiniz ).case object

Bir Java'ya en çok benzeyen bir şey elde etmek için Enum(yani mantıklı toStringve valueOfyöntemlerle - belki de veritabanına enum değerlerini devam ettiriyorsunuz) onu biraz değiştirmeniz gerekir. Eğer skaffman'ın kodunu kullandıysanız:

WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString   //returns Weekday(2)

Halbuki aşağıdaki beyanı kullanırken:

object WeekDay extends Enumeration {
  type WeekDay = Value
  val Mon = Value("Mon")
  val Tue = Value("Tue") 
  ... etc
}

Daha mantıklı sonuçlar elde edersiniz:

WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString   //returns Tue

7
Btw. valueOf yöntemi artık öldü :-(
greenoldman

36
@macias'ın valueOfdeğiştirilmesi, withNamebir Seçenek döndürmez ve eşleşme yoksa bir NSE fırlatır. Ne oldu!
Bluu

6
@Bluu Kendinize valueOf ekleyebilirsiniz: def valueOf (name: String) = WeekDay.values.find (_. ToString == name) bir Seçenek
centr

@centr Bir oluşturmaya çalıştığım Map[Weekday.Weekday, Long]ve buna bir değer eklemeye çalıştığımda Monderleyici geçersiz bir tür hatası atıyor. Hafta içi değer bulundu. Bu neden oluyor?
Sohaib

@Sohaib Harita [Hafta içi.Değeri, Uzun] olmalıdır.
centr

99

Yapmanın birçok yolu var.

1) Semboller kullanın. Bununla birlikte, bir sembolün beklendiği yerlerde sembol olmayanları kabul etmemenin yanı sıra, size herhangi bir tür güvenlik sağlamaz. Burada sadece tamlık için söz ediyorum. İşte bir kullanım örneği:

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) Sınıf kullanma Enumeration:

object Dimension extends Enumeration {
  type Dimension = Value
  val Row, Column = Value
}

veya serileştirmeniz veya görüntülemeniz gerekiyorsa:

object Dimension extends Enumeration("Row", "Column") {
  type Dimension = Value
  val Row, Column = Value
}

Bu şu şekilde kullanılabilir:

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 /

Maalesef, tüm eşleşmelerin dikkate alındığından emin değildir. Eğer maça Satır veya Sütun koymayı unutsaydım, Scala derleyicisi beni uyarmazdı. Bu yüzden bana bir tür güvenlik veriyor , ancak kazanılabilecek kadar değil.

3) Vaka nesneleri:

sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension

Şimdi, a'da bir dava bırakırsam matchderleyici beni uyarır:

MatrixInt.scala:70: warning: match is not exhaustive!
missing combination         Column

    what match {
    ^
one warning found

Hemen hemen aynı şekilde kullanılır ve aşağıdakilere bile gerek yoktur 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 /

Öyleyse neden vaka nesneleri yerine bir Numaralandırma kullandığınızı merak edebilirsiniz. Aslında, vaka nesnelerinin burada olduğu gibi birçok kez avantajları vardır. Enumeration sınıfının, yine de, bir Yineleyici, harita, flatMap, filtre vb. Döndüren öğeler (Scala 2.8'de yineleyici) gibi birçok Koleksiyon yöntemi vardır.

Bu cevap aslında blogumdaki bu makaleden seçilen bir bölüm .


"... bir sembolün beklendiği yerde sembol olmayanları kabul etmemek"> Sanırım Symbolörneklerin boşlukları veya özel karakterleri olamaz. Çoğu insan Symbolsınıfla ilk karşılaştığında muhtemelen böyle düşünür, ama aslında yanlıştır. Symbol("foo !% bar -* baz")derler ve mükemmel çalışır. Başka bir deyişle, herhangi bir dizeyi Symbolsaran mükemmel örnekler oluşturabilirsiniz (bunu "tek koma" sözdizimsel şekerle yapamazsınız). Garanti eden tek şey , herhangi bir sembolün benzersizliğidir, bu da karşılaştırmayı ve eşleştirmeyi marjinal olarak daha hızlı hale getirir. Symbol
Régis Jean-Gilles

@ RégisJean-Gilles Hayır, yani String, örneğin bir Symbolparametreye argüman olarak geçemezsiniz .
Daniel

Evet, bu kısmı anladım, ancak Stringtemelde bir dize etrafındaki bir sarıcı olan ve her iki yönde de serbestçe dönüştürülebilen başka bir sınıfla değiştirirseniz oldukça tartışmalı bir nokta Symbol. Sanırım "Bu size herhangi bir tür güvenlik vermeyecek" derken kastettiğin şey, OP'nin açık bir şekilde güvenli tip çözümler istediği göz önüne alındığında çok açık değildi. Yazarken, sadece güvenli bir tür olmadığını bildiğinizden emin değildim, çünkü bunlar hiç numaralandırma değil, aynı zamanda Symbol geçilen argümanın özel karakterlere sahip olmayacağını bile garanti etmiyor.
Régis Jean-Gilles

1
Ayrıntılı olarak belirtmek gerekirse, "bir sembolün beklendiği yerde sembol olmayanları kabul etmeme" derseniz, "Sembol örneği olmayan değerleri kabul etmeme" ("açıkça doğru olan") veya "olmayan değerleri kabul etmeme" olarak okunabilir. düz tanımlayıcı benzeri dizeleri, aka 'sembolleri'"(doğru değildir ve hemen hemen herkes nedeniyle ilk karşılaşması özel olsa olmasından biz skala sembolleri karşılaşmak ilk kez olan bir yanlış anlaşılma olduğu 'foogösterimde yapar engellemez tanımlayıcı olmayan dizeler). Bu, gelecekteki herhangi bir okuyucu için gidermek istediğim yanlış anlamadır.
Régis Jean-Gilles

@ RégisJean-Gilles Açıkçası doğru olanı kastetmiştim. Demek istediğim, statik yazmaya alışkın olan herkes için kesinlikle doğrudur. O zamanlar statik ve "dinamik" yazmanın göreli değerleri hakkında çok fazla tartışma vardı ve Scala ile ilgilenen birçok insan dinamik bir yazım geçmişinden geldi, bu yüzden söylemeden gitmediğini düşündüm . Bugünlerde bu sözleri söylemeyi bile düşünmezdim. Şahsen, Scala'nın Sembolü çirkin ve gereksiz olduğunu düşünüyorum ve asla kullanmayın. Son yorumunuzu onaylıyorum, çünkü bu iyi bir nokta.
Daniel C.Sobral

52

Adlandırılmış numaralandırmaları bildirmek için biraz daha az ayrıntılı bir yol:

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

Tabii ki burada sorun, ad ve val aynı satırda bildirilirse daha kolay olan adların ve vallerin sırasını senkronize tutmanız gerekecek.


11
Bu ilk bakışta daha temiz görünüyor, ancak bakıcıdan her iki listenin sırasını senkronize tutmasını istemenin dezavantajı var. Haftanın günleri örneği için, bu olası görünmüyor. Ancak genel olarak, yeni bir değer eklenebilir veya biri silinebilir ve iki liste senkronize olmayabilir, bu durumda ince hatalar ortaya çıkabilir.
Brent Faust

1
Önceki yoruma göre, iki farklı listenin sessizce senkronizasyondan çıkması riski vardır. Mevcut küçük örneğiniz için bir sorun olmasa da, daha fazla üye varsa (düzinelerce ila yüzlerce gibi), iki listenin sessizce senkronizasyondan çıkma olasılığı oldukça yüksektir. Ayrıca ölçekleme, Scala'nın derleme zamanı ayrıntılı model eşleştirme uyarıları / hatalarından yararlanamaz. İki listenin senkronize kalmasını sağlamak için bir çalışma zamanı denetimi gerçekleştiren bir çözüm içeren bir StackOverflow yanıtı oluşturdum: stackoverflow.com/a/25923651/501113
chaotic3quilibrium 20:08

17

Numaralandırma yerine kapalı soyut bir sınıf kullanabilirsiniz, örneğin:

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))
  }

}

Büyük / küçük harf nesneleri ile mühürlenmiş özellik de bir olasılıktır.
Ashalynd

2
"Mühürlü özellik + vaka nesneleri" desen bir StackOverflow cevap ayrıntılı olarak sorunları var. Ancak, ben de iş parçacığı kapsamında da bu desen ile ilgili tüm sorunları çözmek için
anladım


2

Scala'daki "numaralandırmalar" çevresindeki tüm seçenekler hakkında kapsamlı bir araştırma yaptıktan sonra, bu alanın başka bir StackOverflow iş parçacığında çok daha eksiksiz bir genel görünümünü yayınladım . JVM sınıfı / nesne başlatma siparişi sorununu çözdüğüm "mühürlü özellik + vaka nesnesi" desenine bir çözüm içerir.



1

Scala'da https://github.com/lloydmeta/enumeratum ile çok rahat

Proje, örnekler ve belgelerle gerçekten iyi

Dokümanlarındaki bu örnek sizi ilgilendirir

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.