Vaka nesneleri ve Scala'daki numaralandırmalar


231

Scala'da Numaralandırmayı genişletme ile vaka sınıflarının (veya vaka nesnelerini) ne zaman kullanacağınıza dair en iyi uygulama yönergeleri var mı?

Aynı faydalardan bazılarını sunuyor gibi görünüyor.


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

1
Ayrıca bkz. Dotty tabanlı Scala 3enum (2020 ortası için).
VonC

Yanıtlar:


223

Büyük bir fark, Enumerationbazı nameString'den onları somutlaştırmak için destekle gelmesidir. Örneğin:

object Currency extends Enumeration {
   val GBP = Value("GBP")
   val EUR = Value("EUR") //etc.
} 

Sonra şunları yapabilirsiniz:

val ccy = Currency.withName("EUR")

Bu, numaralandırmalara (örneğin, bir veritabanına) devam etmek veya dosyalarda bulunan verilerden oluşturmak istediğinizde yararlıdır. Bununla birlikte, genel olarak, numaralandırmaların Scala'da biraz sakar olduğunu ve garip bir eklenti hissi olduğunu düşünüyorum, bu yüzden şimdi case objects kullanma eğilimindeyim . A case objectbir numaradan daha esnektir:

sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.

case class UnknownCurrency(name: String) extends Currency

Şimdi avantajım var ...

trade.ccy match {
  case EUR                   =>
  case UnknownCurrency(code) =>
}

@ Chaotic3quilibrium'un işaret ettiği gibi (okumayı kolaylaştırmak için bazı düzeltmelerle):

"UnknownCurrency (kod)" kalıbı ile ilgili olarak, türün kapalı küme niteliğini "kırmak" dışında bir para birimi kodu dizesi bulamamanın başka yolları da vardır Currency. UnknownCurrencytür olması Currencyartık bir API'nın diğer bölümlerine gizlenebilir.

Bu vakanın dışarıya itilmesi Enumerationve müşterinin Option[Currency]gerçekten eşleşen bir sorun olduğunu açıkça belirten bir türle ilgilenmesi ve API kullanıcısını kendisini sıralaması için "cesaretlendirmesi" önerilir.

Buradaki diğer cevapları takip etmek için, case objects over Enumerations'nin ana dezavantajları şunlardır:

  1. "Numaralandırma" nın tüm örnekleri üzerinde yineleme yapılamaz . Bu kesinlikle doğru, ama pratikte bunun gerekli olduğunu çok nadir buldum.

  2. Kalıcı değerden kolayca somutlaştırılamaz . Bu da geçerlidir, ancak, büyük numaralandırmalar (örneğin, tüm para birimleri) hariç, bu büyük bir ek yük oluşturmaz.


10
Diğer fark, Numaralandırma enumunun kutudan sipariş edilmesidir, oysa nesne nesnesi tabanlı enum açıkçası değil
om-nom-nom

1
Büyük / küçük harf nesneleri için bir başka nokta da, java birlikte çalışabilirliğini önemsemenizdir. Numaralandırma değerleri Enumeration.Value olarak döndürür, böylece 1) skala kitaplığı gerektirir, 2) gerçek tür bilgisini kaybeder.
juanmirocks

7
@oxbow_lakes 1. nokta ile ilgili olarak, özellikle bu kısım "... Pratikte bunun gerekli olduğunu çok nadir buldum": Görünüşe göre nadiren çok fazla UI çalışması yapıyorsunuz. Bu son derece yaygın bir kullanım durumudur; seçim yapılacak geçerli numaralandırma üyelerinin (açılır) bir listesini görüntüleme.
chaotic3quilibrium

trade.ccyMühürlü özellik örneğinde eşleşen öğenin türünü anlamıyorum .
rloth

ve case objectdaha büyük (~ 4x) kod kapladığı alan oluşturmaz Enumerationmı? Özellikle scala.jsaz yer kaplayan projeler için yararlı bir ayrım .
ecoe

69

GÜNCELLEME: Aşağıdaki ana hatları çizdiğim çözümden çok daha üstün olan yeni bir makro tabanlı çözüm oluşturuldu. Bu yeni makro tabanlı çözümü kullanmanızı önemle tavsiye ederim . Ve Dotty için planların bu tarz enum çözümü dilinin bir parçası haline getireceği planları görünüyor. Whoohoo!

Özet:
Java'yı Enumbir Scala projesi içinde yeniden üretmeye çalışmak için üç temel model vardır . Üç modelden ikisi; doğrudan Java kullanarak Enumve scala.EnumerationScala'nın ayrıntılı desen eşleştirmesini etkinleştiremez. Üçüncüsü; "mühürlü özellik + vaka nesnesi", ... ancak JVM sınıfı / nesne başlatma komplikasyonlarına sahip ve bu da tutarsız sıralı dizin oluşturma ile sonuçlanıyor.

İki sınıfla bir çözüm yarattım; Sayım ve EnumerationDecorated , bu yer Gist'e . Numaralandırma dosyası oldukça büyük olduğu için bu iş parçacığının kodunu yayınlamadım (+400 satır - uygulama bağlamını açıklayan birçok yorum içerir).

Ayrıntılar:
Sorduğunuz soru oldukça genel; "... casesınıflar ne zaman objectsuzayacak? [scala.]Enumeration" Ve her bir cevabın sahip olduğunuz belirli proje gereksinimlerinin inceliklerine bağlı olarak ÇOK olası cevaplar olduğu ortaya çıkıyor. Cevap üç temel desene indirgenebilir.

Başlamak için, bir numaralandırmanın ne olduğu hakkında aynı temel fikirden çalıştığımızdan emin olalım. Bir numaralandırmayı daha çok EnumJava 5 (1.5) 'den sağlanan şekilde tanımlayalım :

  1. Doğal sıralı kapalı üyelerden oluşan bir grup içerir
    1. Sabit sayıda üye var
    2. Üyeler doğal olarak sıralanır ve açıkça dizine eklenir
      • Bazı üye üye sorgulanabilir ölçütlerine göre sıralanmanın aksine
    3. Her üyenin, toplam üye kümesinde benzersiz bir adı vardır
  2. Tüm üyeler dizinlerine göre kolayca yinelenebilir
  3. Bir üye (büyük / küçük harfe duyarlı) adıyla alınabilir
    1. Bir üyenin büyük / küçük harf duyarsız adıyla alınabilmesi oldukça hoş olurdu
  4. Bir üye kendi diziniyle alınabilir
  5. Üyeler serileştirmeyi kolay, şeffaf ve verimli bir şekilde kullanabilir
  6. Üyeler ek ilişkili tekli veri elde etmek için kolayca genişletilebilir
  7. Java'nın ötesinde düşünerek Enum, bir numaralandırma için Scala'nın desen eşleştirme yorgunluk kontrolünü açıkça kullanabilmek güzel olurdu.

Ardından, yayınlanan en yaygın üç çözüm modelinin haşlanmış sürümlerine bakalım:

A) Aslında doğrudan JavaEnum desenini kullanarak (karışık bir Scala / Java projesinde):

public enum ChessPiece {
    KING('K', 0)
  , QUEEN('Q', 9)
  , BISHOP('B', 3)
  , KNIGHT('N', 3)
  , ROOK('R', 5)
  , PAWN('P', 1)
  ;

  private char character;
  private int pointValue;

  private ChessPiece(char character, int pointValue) {
    this.character = character; 
    this.pointValue = pointValue;   
  }

  public int getCharacter() {
    return character;
  }

  public int getPointValue() {
    return pointValue;
  }
}

Numaralandırma tanımından aşağıdaki öğeler mevcut değildir:

  1. 3.1 - Bir üyenin büyük / küçük harf duyarsız adıyla alınabilmesi oldukça hoş olurdu
  2. 7 - Java'nın Enum'unun ötesinde düşünmek, bir numaralandırma için Scala'nın desen eşleştirme yorgunluk kontrolünü açıkça kullanabilmek güzel olurdu.

Mevcut projelerim için, Scala / Java karma proje yolunda risk almak gibi bir avantajım yok. Karışık bir proje yapmayı seçebilsem bile, numaralandırma üyeleri eklediğimde / kaldırdığımda veya mevcut numaralandırma üyeleriyle başa çıkmak için yeni bir kod yazıyorsam, öğe 7 derleme zamanı sorunlarını yakalamam için kritik öneme sahip.


B) " sealed trait+case objects " desenini kullanarak :

sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
  case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
  case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
  case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
  case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
  case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
  case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}

Numaralandırma tanımından aşağıdaki öğeler mevcut değildir:

  1. 1.2 - Üyeler doğal olarak sıralanır ve açıkça dizine eklenir
  2. 2 - Tüm üyeler dizinlerine göre kolayca yinelenebilir
  3. 3 - Üye (büyük / küçük harfe duyarlı) adıyla alınabilir
  4. 3.1 - Bir üyenin büyük / küçük harf duyarsız adıyla alınabilmesi oldukça hoş olurdu
  5. 4 - Bir üye kendi diziniyle alınabilir

Numaralandırma tanım öğelerinin 5 ve 6'yı gerçekten karşılaması tartışılabilir. 5 için, etkili olduğunu iddia etmek bir esneme. 6 için, ek ilişkili tekli veri elde etmek için genişletmek gerçekten kolay değil.


C)scala.Enumeration Deseni kullanma ( bu StackOverflow yanıtından esinlenerek ):

object ChessPiece extends Enumeration {
  val KING = ChessPieceVal('K', 0)
  val QUEEN = ChessPieceVal('Q', 9)
  val BISHOP = ChessPieceVal('B', 3)
  val KNIGHT = ChessPieceVal('N', 3)
  val ROOK = ChessPieceVal('R', 5)
  val PAWN = ChessPieceVal('P', 1)
  protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
  implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}

Numaralandırma tanımından aşağıdaki öğeler kullanılamaz (doğrudan Java Numaralandırma'yı kullanmak için listeyle aynıdır):

  1. 3.1 - Bir üyenin büyük / küçük harf duyarsız adıyla alınabilmesi oldukça hoş olurdu
  2. 7 - Java'nın Enum'unun ötesinde düşünmek, bir numaralandırma için Scala'nın desen eşleştirme yorgunluk kontrolünü açıkça kullanabilmek güzel olurdu.

Yine mevcut projelerim için, madde 7, numaralandırma üyeleri eklediğimde / kaldırdığımda veya mevcut numaralandırma üyeleriyle başa çıkmak için yeni bir kod yazdığımda derleme zamanı sorunlarını yakalamama izin vermek için kritik önem taşıyor.


Dolayısıyla, yukarıdaki bir numaralandırma tanımı göz önüne alındığında, yukarıdaki üç çözümün hiçbiri yukarıdaki numaralandırma tanımında özetlenen her şeyi sağlamadığından işe yaramaz:

  1. Java Enum doğrudan karışık bir Scala / Java projesinde
  2. "kapalı özellik + vaka nesneleri"
  3. scala.Enumeration

Bu çözümlerin her biri sonunda, her birinin eksik gereksinimlerinin bir kısmını karşılamaya çalışmak için yeniden işlenebilir / genişletilebilir / yeniden düzenlenebilir. Bununla birlikte, ne Java Enumne de scala.Enumerationçözümler, madde 7'yi sağlamak için yeterince genişletilemez. Ve kendi projelerim için, bu, Scala'da kapalı bir tür kullanmanın daha çekici değerlerinden biridir. Ben bir üretim çalışma zamanı istisna / başarısızlık dışarı çıkarmak zorunda aksine benim kodda bir boşluk / sorun olduğunu belirtmek için zaman uyarıları / hataları derleme tercih ederim.


Bu bağlamda, case objectyukarıdaki tüm numaralandırma tanımını kapsayan bir çözüm üretip üretemeyeceğimi görmek için yolla çalışmaya başladım. İlk zorluk, JVM sınıfı / nesne başlatma sorununun özünü ( bu StackOverflow yayınında ayrıntılı olarak ele alınmıştır) zorlamaktı . Ve sonunda bir çözüm bulabildim.

Benim çözümüm iki özellik olduğu için; Sayım ve EnumerationDecorated , ve o zamandan beri Enumerationsürekli 400 üzerinde satır uzunluğundadır (bağlamını açıklayan yorumlar, bir sürü), (o sayfayı considerbly aşağı streç yapar) bu konuya yapıştırarak forgoing ediyorum. Ayrıntılar için lütfen doğrudan Gist'e atlayın .

İşte çözüm, yukarıdaki ile aynı veri fikrini ( burada tam olarak yorumlanmış sürüm ) kullanıyor ve uygulanmış gibi görünüyor EnumerationDecorated.

import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated

object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
  case object KING extends Member
  case object QUEEN extends Member
  case object BISHOP extends Member
  case object KNIGHT extends Member
  case object ROOK extends Member
  case object PAWN extends Member

  val decorationOrderedSet: List[Decoration] =
    List(
        Decoration(KING,   'K', 0)
      , Decoration(QUEEN,  'Q', 9)
      , Decoration(BISHOP, 'B', 3)
      , Decoration(KNIGHT, 'N', 3)
      , Decoration(ROOK,   'R', 5)
      , Decoration(PAWN,   'P', 1)
    )

  final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
    val description: String = member.name.toLowerCase.capitalize
  }
  override def typeTagMember: TypeTag[_] = typeTag[Member]
  sealed trait Member extends MemberDecorated
}

Bu, numaralandırma tanımında istenen ve ana hatlarıyla belirtilen tüm yetenekleri uygulamak için oluşturduğum yeni bir numaralandırma özelliğinin ( bu Gist'te bulunan ) bir çift kullanımının bir örneğidir .

Belirtilen bir endişe, numaralandırma üye adlarının tekrarlanması gerektiğidir ( decorationOrderedSetyukarıdaki örnekte). Tek bir tekrarı en aza indirgeme rağmen, iki sorun nedeniyle nasıl daha az hale getirileceğini göremedim:

  1. Bu belirli nesne / vaka nesnesi modeli için JVM nesnesi / sınıf başlatma tanımsız ( bu Stackoverflow iş parçacığına bakın )
  2. Yöntemden döndürülen içeriğin getClass.getDeclaredClassestanımlanmamış bir sırası var (ve case objectkaynak kodundaki bildirimlerle aynı sırada olması pek olası değildir )

Bu iki sorun göz önüne alındığında, zımni bir sipariş oluşturmaya çalışmaktan vazgeçmek zorunda kaldım ve müşterinin bir tür düzenli set kavramı ile tanımlamasını ve beyan etmesini açıkça zorunlu kılmak zorunda kaldım. Scala koleksiyonları bir insert sipariş seti uygulaması olmadığından, yapabileceğim en iyi şey Listbir set olduğunu ve daha sonra gerçekten bir set olup olmadığını kontrol etmekti. Bunu başarmayı tercih etmem böyle olmazdı.

Ve tasarım bu ikinci liste / set sırasını gerektirdiğinde val, ChessPiecesEnhancedDecoratedyukarıdaki örnek verildiğinde, eklemek case object PAWN2 extends Memberve sonra eklemeyi unutmak mümkün Decoration(PAWN2,'P2', 2)oldu decorationOrderedSet. Bu nedenle, listenin yalnızca bir küme olmadığını, aynı zamanda genişleten büyük TÜM nesnelerin içerdiğini doğrulamak için bir çalışma zamanı denetimi vardır sealed trait Member. Bu, üzerinde çalışılacak özel bir yansıma / makro cehennem biçimiydi.


İlgili yorum ve / veya geri bırakınız Gist'e .


Şimdi ScalaOlio kütüphanede hem daha yukarı güncel sürümlerini içeren (GPLv3) ilk sürümünü yayınladı org.scalaolio.util.Enumerationve org.scalaolio.util.EnumerationDecorated: scalaolio.org
chaotic3quilibrium

Ve doğrudan Github'daki ScalaOlio deposuna atlamak için: github.com/chaotic3quilibrium/scala-olio
chaotic3quilibrium

5
Bu kaliteli bir cevap ve ondan alınacak çok şey var. Teşekkür ederim
angabriel

1
Görünüşe göre Odersky, Dotty'yi (gelecekteki Scala 3.0) yerel bir numaralandırma ile yükseltmek istiyor. Whoohoo! github.com/lampepfl/dotty/issues/1970
chaotic3quilibrium

62

Büyük / küçük harf nesneleri zaten toString yöntemleri için adlarını döndürür, bu yüzden ayrı ayrı iletmek gereksizdir. Jho'nunkine benzer bir versiyon (kısalık için uygunluk yöntemleri yok):

trait Enum[A] {
  trait Value { self: A => }
  val values: List[A]
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
  val values = List(EUR, GBP)
}

Nesneler tembeldir; vals kullanarak listeyi bırakabiliriz, ancak adı tekrarlamamız gerekir:

trait Enum[A <: {def name: String}] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed abstract class Currency(val name: String) extends Currency.Value
object Currency extends Enum[Currency] {
  val EUR = new Currency("EUR") {}
  val GBP = new Currency("GBP") {}
}

Hile yapmanın bir sakıncası yoksa, yansıma API'sını veya Google Reflections gibi bir şeyi kullanarak numaralandırma değerlerinizi önceden yükleyebilirsiniz. Tembel olmayan vaka nesneleri size en temiz sözdizimini sağlar:

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
}

Vaka sınıflarının ve Java numaralandırmalarının tüm avantajlarıyla güzel ve temiz. Şahsen, idiyomatik Scala koduyla daha iyi eşleşmek için nesne dışındaki numaralandırma değerlerini tanımlarım:

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency

3
bir soru: son çözüm "tembel olmayan vaka nesneleri" olarak adlandırılır, ancak bu durumda nesneler kullanılıncaya kadar yüklenmez: neden bu çözümü tembel değil olarak adlandırıyorsunuz?
Seb Cesbron

2
@Noel, kullanmanız gerekir: tüm mühürlü hiyerarşiyi REPL'e yapıştırmak için yapıştırın. Bunu yapmazsanız, kapalı temel sınıf / özelliğe sahip tek satır hemen tek bir dosya olarak sayılır, hemen mühürlenir ve bir sonraki satırda genişletilemez.
Jürgen Strobel

2
@GatesDA Yalnızca ilk kod snippet'inizde bir hata yok (istemcinin değerleri beyan etmesini ve tanımlamasını açıkça gerektirdiğiniz için. Hem ikinci hem de üçüncü çözümlerinizin son yorumunda açıkladığım küçük bir hata var (müşteri Para Birimi'ne erişirse) .GBP doğrudan ve önce, değerler Listesi "arızalı" olacaktır) Scala numaralandırma alanını kapsamlı bir şekilde araştırdım ve aynı konuya verdiğim cevabı ayrıntılı olarak ele aldım: stackoverflow.com/a/25923651/501113
chaotic3quilibrium

1
Belki de bu yaklaşımın dezavantajlarından biri (yine de Java Enums ile karşılaştırıldığında) IDE'ye Para Birimi <dot> yazdığınızda kullanılabilir seçenekler göstermemesidir.
Ivan Balashov

1
@SebCesbron'un belirttiği gibi, vaka nesneleri burada tembeldir. Bu nedenle, ararsam Currency.values, yalnızca daha önce eriştiğim değerleri geri alırım. Bunun etrafında bir yol var mı?
Sasgorilla

27

Vaka sınıflarını Numaralandırmalara göre kullanmanın avantajları şunlardır:

  • Mühürlü vaka sınıflarını kullanırken, Scala derleyicisi eşleşmenin tam olarak belirtilip belirtilmediğini söyleyebilir; örneğin, olası tüm eşleşmeler eşleştirme bildiriminde desteklendiğinde. Numaralandırmalarda Scala derleyicisi bunu söyleyemez.
  • Vaka sınıfları doğal olarak bir adı ve kimliği destekleyen Değer Tabanlı Numaralandırmadan daha fazla alanı destekler.

Vaka sınıfları yerine Numaralandırma kullanmanın avantajları şunlardır:

  • Numaralandırma genellikle yazmak için biraz daha az kod olacaktır.
  • Scala'da yeni olan biri için numaralandırmaların anlaşılması biraz daha kolaydır, çünkü diğer dillerde yaygındır

Bu nedenle, genel olarak, ada göre basit sabitler listesine ihtiyacınız varsa, numaralandırma kullanın. Aksi takdirde, biraz daha karmaşık bir şeye ihtiyacınız varsa veya derleyicinin ekstra güvenliğinin belirtilmiş tüm eşleşmelerinizin olup olmadığını size bildirmesini istiyorsanız, vaka sınıflarını kullanın.


15

GÜNCELLEME: Aşağıdaki kodun burada açıklanan bir hatası vardır . Aşağıdaki test programı çalışır, ancak DayOfWeek'in kendisinden önce DayOfWeek.Mon (örneğin) kullanacak olsaydınız, DayOfWeek başlatılmadığı için başarısız olur (bir iç nesnenin kullanılması bir dış nesnenin başlatılmasına neden olmaz). val enums = Seq( DayOfWeek )Ana sınıfınızda olduğu gibi bir şey yaparsanız , numaralarınızın başlatılmasını zorlarsanız veya chaotic3quilibrium'un değişikliklerini kullanabilirsiniz. Makro tabanlı bir numaralandırma bekliyorum!


Eğer istersen

  • ayrıntılı olmayan model eşleşmeleri hakkında uyarılar
  • isteğe bağlı olarak kontrol edebileceğiniz her bir enum değerine atanan bir Int ID
  • enum değerlerinin değişmez bir listesi tanımlandıkları sıraya göre
  • isminden enum değerine değişmez bir harita
  • id'den enum değerine değişmez bir harita
  • tüm veya belirli numaralandırma değerleri veya bir bütün olarak numaralandırma için yöntem / veri yapıştırma yerleri
  • sıralı numaralandırma değerleri (örneğin, günün <Çarşamba günü olup olmadığını test edebilirsiniz)
  • bir enum'u başkalarını yaratmak için genişletme yeteneği

o zaman aşağıdakiler ilgi çekici olabilir. Geribildirim hoş geldiniz.

Bu uygulamada, genişlettiğiniz soyut Enum ve EnumVal temel sınıfları vardır. Bu sınıfları bir dakika içinde göreceğiz, ama önce bir numaralandırma şöyle tanımlanacaktı:

object DayOfWeek extends Enum {
  sealed abstract class Val extends EnumVal
  case object Mon extends Val; Mon()
  case object Tue extends Val; Tue()
  case object Wed extends Val; Wed()
  case object Thu extends Val; Thu()
  case object Fri extends Val; Fri()
  case object Sat extends Val; Sat()
  case object Sun extends Val; Sun()
}

Her bir enum değerini hayata geçirmek için (uygula yöntemini çağır) kullanmanız gerektiğini unutmayın. [Keşke özellikle olmasını istemedikçe iç nesneler tembel olmasaydı. Bence.]

İstediğimiz takdirde elbette DayOfWeek, Val veya tekil vaka nesnelerine yöntemler / veriler ekleyebiliriz.

İşte böyle bir numaralandırmayı nasıl kullanacaksınız:

object DayOfWeekTest extends App {

  // To get a map from Int id to enum:
  println( DayOfWeek.valuesById )

  // To get a map from String name to enum:
  println( DayOfWeek.valuesByName )

  // To iterate through a list of the enum values in definition order,
  // which can be made different from ID order, and get their IDs and names:
  DayOfWeek.values foreach { v => println( v.id + " = " + v ) }

  // To sort by ID or name:
  println( DayOfWeek.values.sorted mkString ", " )
  println( DayOfWeek.values.sortBy(_.toString) mkString ", " )

  // To look up enum values by name:
  println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val]
  println( DayOfWeek("Xyz") ) // None

  // To look up enum values by id:
  println( DayOfWeek(3) )         // Some[DayOfWeek.Val]
  println( DayOfWeek(9) )         // None

  import DayOfWeek._

  // To compare enums as ordinals:
  println( Tue < Fri )

  // Warnings about non-exhaustive pattern matches:
  def aufDeutsch( day: DayOfWeek.Val ) = day match {
    case Mon => "Montag"
    case Tue => "Dienstag"
    case Wed => "Mittwoch"
    case Thu => "Donnerstag"
    case Fri => "Freitag"
 // Commenting these out causes compiler warning: "match is not exhaustive!"
 // case Sat => "Samstag"
 // case Sun => "Sonntag"
  }

}

İşte derlediğinizde:

DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination            Sat
missing combination            Sun

  def aufDeutsch( day: DayOfWeek.Val ) = day match {
                                         ^
one warning found

Bu tür uyarıları istemediğiniz "gün eşleşmesi" ni "(gün: @ denetlenen) eşleşmesi" ile değiştirebilir veya sonuna tümünü yakalama vakası ekleyebilirsiniz.

Yukarıdaki programı çalıştırdığınızda, bu çıktıyı alırsınız:

Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri)
Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri)
0 = Mon
1 = Tue
2 = Wed
3 = Thu
4 = Fri
5 = Sat
6 = Sun
Mon, Tue, Wed, Thu, Fri, Sat, Sun
Fri, Mon, Sat, Sun, Thu, Tue, Wed
Some(Tue)
None
Some(Thu)
None
true

Liste ve Haritalar'ın değiştirilemediğinden, numaralandırmayı bozmadan alt kümeler oluşturmak için öğeleri kolayca kaldırabileceğinizi unutmayın.

İşte Enum sınıfının kendisi (ve içindeki EnumVal):

abstract class Enum {

  type Val <: EnumVal

  protected var nextId: Int = 0

  private var values_       =       List[Val]()
  private var valuesById_   = Map[Int   ,Val]()
  private var valuesByName_ = Map[String,Val]()

  def values       = values_
  def valuesById   = valuesById_
  def valuesByName = valuesByName_

  def apply( id  : Int    ) = valuesById  .get(id  )  // Some|None
  def apply( name: String ) = valuesByName.get(name)  // Some|None

  // Base class for enum values; it registers the value with the Enum.
  protected abstract class EnumVal extends Ordered[Val] {
    val theVal = this.asInstanceOf[Val]  // only extend EnumVal to Val
    val id = nextId
    def bumpId { nextId += 1 }
    def compare( that:Val ) = this.id - that.id
    def apply() {
      if ( valuesById_.get(id) != None )
        throw new Exception( "cannot init " + this + " enum value twice" )
      bumpId
      values_ ++= List(theVal)
      valuesById_   += ( id       -> theVal )
      valuesByName_ += ( toString -> theVal )
    }
  }

}

Ve burada kimlikleri kontrol eden ve Val soyutlamasına ve numaralandırmanın kendisine veri / yöntem ekleyen daha gelişmiş bir kullanım:

object DayOfWeek extends Enum {

  sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal {
    def isWeekend = !isWeekday
    val abbrev = toString take 3
  }
  case object    Monday extends Val;    Monday()
  case object   Tuesday extends Val;   Tuesday()
  case object Wednesday extends Val; Wednesday()
  case object  Thursday extends Val;  Thursday()
  case object    Friday extends Val;    Friday()
  nextId = -2
  case object  Saturday extends Val(false); Saturday()
  case object    Sunday extends Val(false);   Sunday()

  val (weekDays,weekendDays) = values partition (_.isWeekday)
}

Bunu sağlamak için Tyvm. Gerçekten onu takdir ederim. Ancak, val yerine "var" kullandığını fark ediyorum. Ve bu FP dünyasındaki sınırda ölümlü bir günahtır. Peki, bunu kullanmanın bir yolu var mı? Sadece bir çeşit FP tipi kenar durumu olup olmadığını merak ediyorum ve uygulamanızın FP'nin nasıl istenmeyen olduğunu anlamadım.
chaotic3quilibrium

2
Muhtemelen sana yardım edemem. Scala'da dahili olarak mutasyona uğrayan ancak bunları kullananlara değişmeyen sınıflar yazmak oldukça yaygındır. Yukarıdaki örnekte, DayOfWeek kullanıcısı numaralandırmayı değiştiremez; örneğin, Salı'nın kimliğini veya adını bundan sonra değiştirmenin bir yolu yoktur. Ancak dahili olarak mutasyon içermeyen bir uygulama istiyorsanız , o zaman hiçbir şeyim yok. Yine de, 2.11 makroları dayalı güzel bir yeni numaralandırma tesisi görmek için sürpriz olmaz; fikirler skala üzerinde dolaşıyor.
AmigoNico

Scala Çalışma Sayfasında garip bir hata alıyorum. Doğrudan Value örneklerinden birini kullanırsam, başlatma hatası alıyorum. Ancak, numaralandırma içeriğini görmek için .values ​​yöntemine bir çağrı yaparsanız, çalışır ve daha sonra doğrudan value örneği kullanarak çalışır. Başlatma hatasının ne olduğu hakkında bir fikrin var mı? Ve ilk çağrıyı, çağrı kuralına bakılmaksızın uygun sırada olmasını sağlamak için en uygun yol nedir?
chaotic3quilibrium

@ chaotic3quilibrium: Vay canına! Bunu takip ettiğiniz için teşekkür ederim ve elbette ağır kaldırma için Rex Kerr'a teşekkürler. Burada problemden bahsedeceğim ve yarattığınız soruya bakacağım.
AmigoNico

"[Kullanmak var] FP dünyasında sınırda ölümlü bir günahtır" - Bu fikrin evrensel olarak kabul edildiğini düşünmüyorum.
Erik Kaplun

12

Burada, kendi değerler listenizi korumak zorunda kalmadan mühürlü özellikleri / sınıfları enum değerleri olarak kullanmanıza izin veren güzel bir basit lib var. Buggy'ye bağlı olmayan basit bir makroya dayanır knownDirectSubclasses.

https://github.com/lloydmeta/enumeratum


10

Güncelleme Mart 2017: tarafından yorumladı olarak Anthony Accioly , scala.Enumeration/enumPR kapatıldı.

Dotty (Scala için yeni nesil derleyici), dotty sayı 1970 ve Martin Odersky'nin PR 1958'i yönetecek .


Not: Artık (Ağustos 2016, 6+ yıl sonra) kaldırılacak bir teklif var scala.Enumeration: PR 5352

Kullanımdan kaldırıldı scala.Enumeration, ek @enumaçıklama ekle

Sözdizimi

@enum
 class Toggle {
  ON
  OFF
 }

olası bir uygulama örneğidir, niyet ayrıca belirli kısıtlamalara (yuvalama, özyineleme veya değişen kurucu parametreleri yok) uyan ADT'leri desteklemektir, örneğin:

@enum
sealed trait Toggle
case object ON  extends Toggle
case object OFF extends Toggle

Zarar görmemiş felaketi ortadan kaldırır scala.Enumeration.

@Enum'un skalaya göre avantajları.

  • Aslında çalışıyor
  • Java birlikte çalışma
  • Silme sorunu yok
  • Numaralandırma tanımlarken öğrenmek için kafa karıştırıcı mini DSL yok

Dezavantajları: Yok.

Bu, Scala-JVM Scala.jsve Scala-Native'ı destekleyen bir kod tabanına sahip olamama sorununu giderir (Java kaynak kodu desteklenmez Scala.js/Scala-Native, Scala kaynak kodu Scala-JVM üzerindeki mevcut API'lar tarafından kabul edilen numaralandırmaları tanımlayamaz).


Yukarıdaki PR kapatıldı (sevinç yok). Şimdi 2017 ve Dotty sonunda bir enum yapısı alacak gibi görünüyor. İşte sorun ve Martin PR . Birleştir, birleştir, birleştir!
Anthony Accioly

8

Tüm örneklerde yineleme yapmanız veya filtrelemeniz gerektiğinde, vaka sınıflarının Numaralandırmalara göre bir başka dezavantajı. Bu, yerleşik Sınıflandırma (ve Java numaralandırmalar) özelliğidir, vaka sınıfları bu özelliği otomatik olarak desteklemez.

Başka bir deyişle: "vaka sınıflarıyla birlikte numaralandırılmış toplam değerler kümesinin bir listesini almanın kolay bir yolu yoktur".


5

Diğer JVM dilleriyle (örneğin Java) birlikte çalışabilirliği sağlama konusunda ciddiyseniz, Java enums yazmak en iyi seçenektir. Bunlar şeffaf bir şekilde hem Scala hem de Java kodundan çalışırlar scala.Enumeration; Kaçınılabilirse, GitHub'daki her yeni hobi projesi için yeni bir numaralandırma kütüphanesine sahip olmayalım!


4

Bir vaka sınıfını taklit etmenin çeşitli versiyonlarını gördüm. İşte benim sürüm:

trait CaseEnumValue {
    def name:String
}

trait CaseEnum {
    type V <: CaseEnumValue
    def values:List[V]
    def unapply(name:String):Option[String] = {
        if (values.exists(_.name == name)) Some(name) else None
    }
    def unapply(value:V):String = {
        return value.name
    }
    def apply(name:String):Option[V] = {
        values.find(_.name == name)
    }
}

Bu, aşağıdakine benzeyen vaka sınıfları oluşturmanıza olanak tanır:

abstract class Currency(override name:String) extends CaseEnumValue {
}

object Currency extends CaseEnum {
    type V = Site
    case object EUR extends Currency("EUR")
    case object GBP extends Currency("GBP")
    var values = List(EUR, GBP)
}

Belki birisi benim yaptığım gibi listeye her vaka sınıfını eklemekten daha iyi bir numara yapabilirdi. O zaman ortaya çıkabilecek tek şey buydu.


Neden iki ayrı uygulama yöntemi tho?
Saish

@jho Çözümünüzü olduğu gibi çözmeye çalışıyorum, ancak derlenmeyecek. İkinci kod snippitinde, "V = Site" türünde Site'ye bir başvuru vardır. Derleme hatasını temizlemek için neye atıfta bulunduğundan emin değilim. Daha sonra, neden "soyut sınıf Para Birimi" için boş parantez sağlıyorsunuz? Sadece bırakılamazlar mı? Son olarak, neden "var değerleri = ..." içinde bir var kullanıyorsunuz? Bu, istemcilerin kodun herhangi bir yerinden herhangi bir zamanda değerlere yeni bir Liste atayabilecekleri anlamına gelmez mi? Bir var yerine bir val yapmak çok tercih edilmez mi?
chaotic3quilibrium

2

Onlara ihtiyaç duyduğum son birkaç kez bu iki seçenek üzerinde gidip geliyorum. Yakın zamana kadar, tercihim kapalı özellik / vaka nesnesi seçeneği oldu.

1) Scala Numaralandırma Beyanı

object OutboundMarketMakerEntryPointType extends Enumeration {
  type OutboundMarketMakerEntryPointType = Value

  val Alpha, Beta = Value
}

2) Mühürlü Özellikler + Vaka Nesneleri

sealed trait OutboundMarketMakerEntryPointType

case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType

case object BetaEntryPoint extends OutboundMarketMakerEntryPointType

Bunların hiçbiri bir java numaralandırmasının size sunduğu şeylerin hepsini karşılamasa da, aşağıda artıları ve eksileri vardır:

Scala Sayımı

Artıları: -Opsiyonla örnekleme veya doğrudan doğru kabul etme işlevleri (kalıcı bir mağazadan yükleme yaparken daha kolay) -Mümkün olan tüm değerler üzerinde yineleme desteklenir

Eksileri: -Ayrıntılı olmayan arama için derleme uyarısı desteklenmez (kalıp eşleşmesini daha az ideal hale getirir)

Olgu Nesneleri / Mühürlü özellikler

Artıları: -Hafalı özellikleri kullanarak, bazı değerleri önceden oluşturabilirken, diğerleri oluşturma zamanında enjekte edilebilir - desen eşleşmesi için tam destek (tanımlanan / uygulanan yöntemleri uygulayın)

Eksileri: -Sürekli bir mağazadan başlatırken - genellikle burada desen eşleşmesini kullanmanız veya tüm olası 'numaralandırma değerleri' listenizi tanımlamanız gerekir

Nihayetinde fikrimi değiştirmemi sağlayan şey, aşağıdaki pasaj gibi bir şeydi:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type"))
    val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

object InstrumentType {
  def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD)
  .find(_.toString == instrumentType).get
}

object ProductType {

  def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index)
  .find(_.toString == productType).get
}

.getAramalar iğrenç idi - aşağıdaki şekilde yerine ben sadece numaralandırma üzerinde withName yöntemini çağırabilirsiniz numaralandırma kullanarak:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type"))
    val productType = ProductType.withName(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

Bu nedenle, tercihimin ileride değerlerin bir depodan ve vaka nesnelerinden / mühürlü özelliklerden erişilmesi amaçlandığında Numaralandırmaları kullanmak olduğunu düşünüyorum.


Nasıl ikinci kod deseni arzu edilir görebilirsiniz (iki yardımcı yöntemlerden ilk kod desen kurtulmak). Ancak, bu iki model arasında seçim yapmak zorunda kalmayacağınız bir yol buldum. Bu konuya gönderdiğim cevaptaki tüm alanı kapsıyorum: stackoverflow.com/a/25923651/501113
chaotic3quilibrium

2

Ben tercih ederim case objects(kişisel tercih meselesi). Bu yaklaşımın doğasında var olan problemlerle başa çıkmak için (tüm unsurlar üzerinde dize ayrıştır ve yinele), mükemmel olmayan ancak etkili olan birkaç satır ekledim.

Ben burada yararlı olabilir ve ayrıca başkalarının geliştirmek olabilir bekliyor kod yapıştırıyorum.

/**
 * Enum for Genre. It contains the type, objects, elements set and parse method.
 *
 * This approach supports:
 *
 * - Pattern matching
 * - Parse from name
 * - Get all elements
 */
object Genre {
  sealed trait Genre

  case object MALE extends Genre
  case object FEMALE extends Genre

  val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects

  def apply (code: String) =
    if (MALE.toString == code) MALE
    else if (FEMALE.toString == code) FEMALE
    else throw new IllegalArgumentException
}

/**
 * Enum usage (and tests).
 */
object GenreTest extends App {
  import Genre._

  val m1 = MALE
  val m2 = Genre ("MALE")

  assert (m1 == m2)
  assert (m1.toString == "MALE")

  val f1 = FEMALE
  val f2 = Genre ("FEMALE")

  assert (f1 == f2)
  assert (f1.toString == "FEMALE")

  try {
    Genre (null)
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  try {
    Genre ("male")
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  Genre.elements.foreach { println }
}

0

GatesDa'nın işe nasıl cevap alacağını hala arayanlar için : Yalnızca vaka nesnesini, başlatmayı bildirdikten sonra başvurabilirsiniz:

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency; 
  EUR //THIS IS ONLY CHANGE
  case object GBP extends Currency; GBP //Inline looks better
}

0

Ben sahip olmanın en büyük avantajı düşünüyorum case classesüzerinde enumerationskullanabileceğiniz olmasıdır tip sınıfı modelini aka geçici poliforfizmi . Gibi numaralandırmaları eşleştirmenize gerek yok:

someEnum match {
  ENUMA => makeThis()
  ENUMB => makeThat()
}

onun yerine şöyle bir şey olacak:

def someCode[SomeCaseClass](implicit val maker: Maker[SomeCaseClass]){
  maker.make()
}

implicit val makerA = new Maker[CaseClassA]{
  def make() = ...
}
implicit val makerB = new Maker[CaseClassB]{
  def make() = ...
}
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.