web-dev-qa-db-ja.com

Scalaの列挙型にメソッドを追加するにはどうすればよいですか?

Javaでは、次のことができます。

public enum Enum {
    ONE {
        public String method() {
            return "1";
        }
    },
    TWO {
        public String method() {
            return "2";
        }
    },
    THREE {
        public String method() {
            return "3";
        }
    };

    public abstract String method();
}

Scalaでこれをどのように行いますか?

編集/便利なリンク:

38
Etam
object Unit extends Enumeration {
  abstract class UnitValue(var name: String) extends Val(name) {
    def m: Unit
  }
  val G = new UnitValue("g") {
    def m {
        println("M from G")
    }
  }
  val KG = new UnitValue("kg") {
    def m {
        println("M from KG")
    }
  }
}
3
Etam

これは、Enumeration.Valクラスを拡張してscala列挙型に属性を追加する例です。

object Planet extends Enumeration { 
   protected case class Val(val mass: Double, val radius: Double) extends super.Val { 
     def surfaceGravity: Double = Planet.G * mass / (radius * radius) 
     def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity 
   } 
   implicit def valueToPlanetVal(x: Value) = x.asInstanceOf[Val] 

   val G: Double = 6.67300E-11 
   val Mercury = Val(3.303e+23, 2.4397e6) 
   val Venus   = Val(4.869e+24, 6.0518e6) 
   val Earth   = Val(5.976e+24, 6.37814e6) 
   val Mars    = Val(6.421e+23, 3.3972e6) 
   val Jupiter = Val(1.9e+27, 7.1492e7) 
   val Saturn  = Val(5.688e+26, 6.0268e7) 
   val Uranus  = Val(8.686e+25, 2.5559e7) 
   val Neptune = Val(1.024e+26, 2.4746e7) 
} 

scala> Planet.values.filter(_.radius > 7.0e6) 
res16: Planet.ValueSet = Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune) 
36
Sean Ross

Chrisのソリューション に基づいて構築すると、暗黙の変換を使用して、より優れた構文を実現できます。

object Suit extends Enumeration {
   val Clubs, Diamonds, Hearts, Spades = Value

   class SuitValue(suit: Value) {
      def isRed = !isBlack
      def isBlack = suit match {
         case Clubs | Spades => true
         case _              => false
      }
   }

   implicit def value2SuitValue(suit: Value) = new SuitValue(suit)
} 

次に、たとえばSuit.Clubs.isRedを呼び出すことができます。

34
Aaron Novstrup

Scala enumerations はJava enumerationsとは異なります。

現時点では、メソッドを(正気の方法で)追加する方法はありません。いくつかの回避策がありますが、すべての場合およびで機能するものは構文上のゴミのようには見えません。

私は似たようなことを試みました(実行時に新しいインスタンスを作成でき、クラスのobjectsとnewインスタンスの間に機能する同等の関係を持ちながら、クラスの列挙されたインスタンスにメソッドを追加します) 、しかしバグによって停止されました #402 ( "getClasses/getDeclaredClassesは、宣言された一部の(REPL)またはすべての(scalac)クラス(オブジェクト)を見逃しているようです")。

私によるこれらの関連する質問を見てください:

正直なところ、私はEnumerationを使用しません。これはScala 1.0(2004)に由来するクラスであり、奇妙なものが含まれており、最初にチュートリアルなしで使用する方法を理解している人は多くありません(それを書いた人を除く)。

どうしても列挙型が必要な場合は、そのクラスをJavaで記述します。

12
soc

列挙値を反復処理したり、その他の列挙型の処理を実行したりする必要がない場合は、Enumerationの代わりにADTを使用することをお勧めします。

sealed abstract class Enum {
  def method: String = this match {
    case One => "1"
    case Two => "2"
    case Three => "3"
  }
}
case object One extends Enum
case object Two extends Enum
case object Three extends Enum

このアプローチには、Enumeration式の1つ以上のケースを忘れたときにコンパイラーが警告するという、matchに比べて1つの利点があります。

11
missingfaktor

Aaronのソリューション について詳しく説明します。Scala 2.10、 暗黙のクラス を使用したさらにコンパクトな形式です。

object Suit extends Enumeration {
   val Clubs, Diamonds, Hearts, Spades = Value

   implicit class SuitValue(suit: Value) {
      def isRed = !isBlack
      def isBlack = suit match {
         case Clubs | Spades => true
         case _              => false
      }
   }
} 

そして、次のように使用できます:Suit.Clubs.isRed

9
ozeebee

あなたはこれを行うことができます:

object Suit extends Enumeration {
  val Clubs, Diamonds, Hearts, Spades = Value

  def isRed(suit : Value) = !isBlack(suit)
  def isBlack(suit : Value) = suit match {
    case Clubs | Spades => true
    case _              => false
  }
}

明らかにこれは完璧ではありませんが、次のことができます。

Suit.isBlack(Suit.Clubs)
7
oxbow_lakes

Scalaの列挙では、プロパティやメソッドを列挙の値に追加することはできません。この新しいMyEnumerationを使用すると、次のことができます。

abstract class MyEnumeration {
  // "Value" must be the name of the class defining your values type Value
  type Value

  // Contains your values in definition order
  private val vals = collection.mutable.LinkedHashMap[String, Value]()

  // A mixin for your values class to automatically collect the values
  protected trait ValuesCollector { self: Value =>
    private val ordinal = vals.size

    vals += (fieldNames(ordinal) -> self)

    def getName = fieldNames(ordinal)
    override def toString = getName
  }

  def apply(ordinal: Int) = vals(fieldNames(ordinal))
  def apply(fldName: String) = vals(fldName)

  def values = vals.values
  def namedValues: collection.Map[String, Value] = vals

  // Getting the field names through reflection.
  // Copied from scala.Enumeration
  private val fieldNames = getClass.getMethods filter (m =>
    m.getParameterTypes.isEmpty &&
    classOf[ValuesCollector].isAssignableFrom(m.getReturnType) &&
    m.getDeclaringClass != classOf[MyEnumeration]) map (_.getName)

}

ここに、ScalaのPlanetの例があります。

object Planet extends MyEnumeration {

  case class Value(val mass: Double, val radius: Double) extends ValuesCollector {
    // universal gravitational constant  (m3 kg-1 s-2)
    private val G = 6.67300E-11;

    def surfaceGravity = G * mass / (radius * radius)
    def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity

  }

  val MERCURY = Value(3.303e+23, 2.4397e6)
  val VENUS = Value(4.869e+24, 6.0518e6)
  val EARTH = Value(5.976e+24, 6.37814e6)
  val MARS = Value(6.421e+23, 3.3972e6)
  val JUPITER = Value(1.9e+27, 7.1492e7)
  val SATURN = Value(5.688e+26, 6.0268e7)
  val URANUS = Value(8.686e+25, 2.5559e7)
  val Neptune = Value(1.024e+26, 2.4746e7)
  val PLUTO = Value(1.27e+22, 1.137e6)

}

object PlanetTest {
  def main(args: Array[String]) {
    val earthWeight = 175
    val mass = earthWeight/Planet.EARTH.surfaceGravity
    for (p <- Planet.values) println("Your weight on %s is %f" format (p, p.surfaceWeight(mass)))
    /* Your weight on MERCURY is 66.107583
     * Your weight on VENUS is 158.374842
     * Your weight on EARTH is 175.000000
     * Your weight on MARS is 66.279007
     * Your weight on JUPITER is 442.847567
     * Your weight on SATURN is 186.552719
     * Your weight on URANUS is 158.397260
     * Your weight on Neptune is 199.207413
     * Your weight on PLUTO is 11.703031
     */
  }

} 
4

列挙値ごとにメソッドが絶対に必要な場合および値を反復処理できる必要がある場合は、次のようにすることができます。

object BatchCategory extends Enumeration {
  class BatchCategory extends Val {
    val isOfficial, isTest, isUser = false
  }

  val OFFICIAL = new BatchCategory { override val isOfficial = true }
  val TEST =     new BatchCategory { override val isTest = true }
  val USER =     new BatchCategory { override val isUser = true }

  // Needed to get BatchCategory from Enumeration.values
  implicit def valueToBatchCategory(v: Value): BatchCategory = v match {
    case bc: BatchCategory => bc
    case x => throw new IllegalArgumentException("Value is not a BatchCategory: " + x)
  }

  def valueOf(catStr: String): BatchCategory = {
    BatchCategory.values.
      find { v => val s = v.toString; s.take(1) == catStr || s == catStr }.
      getOrElse(throw new IllegalArgumentException("Unknown category '" + catStr + "' !  "))
  }

  def main(args: Array[String]) {
    BatchCategory.values.foreach(v => println(v + " isOfficial=" + v.isOfficial))
  }
}

プリント

OFFICIAL isOfficial=true
TEST isOfficial=false
USER isOfficial=false

これは、列挙型以外の単純な列挙型戦略に移動できなかった一部のレガシーコードに対して行われました。

1
sourcedelica

Scala.Enumerationのソースコードをチェックした後、私はこれを手に入れました:


object MyEnum extends Enumeration {
  val ONE = new Val { def method = "1" }
  val TWO = new Val { def method = "2" }
  val THREE = new Val { def method = "3" }
}

匿名化されたクラスが使用されているため、「new」を取り除くのは難しいようです。誰かがそれを行う方法を知っているなら、私に知らせてください:)

1
Shiva Wu

回答、Scala列挙型はargs/methods-customizedvaluesをサポートしていません は間違っているようです。他の回答(一部にはimplicitが含まれます)それができることを示しますが、名前の重複を要求する印象を生み出します。値は名前をJavaオブジェクトフィールドとして宣言し、次に、名前は文字列として値コンストラクターに提供されますが、ポイント全体はof Enumsは、反復可能な名前->値マップを作成することであり、scalaは冗長性なしで実行できます:

object Ops1 extends Enumeration {

    protected case class OpsVal(f: Int => Int) extends super.Val(/*nextId*/)

    val ZERO = new FuncVal (x => 0)
    val DOUBLE = new FuncVal (x => 2 * x )

    implicit def convert(v: Value) = v.asInstanceOf[OpsVal]

}

// implicit is not needed
Ops1.ZERO.f(1)                            //> res0: Int = 0

// implicit is needed
Ops1.values map (v => (v + "=>" + v.f(1)))//> res1: scala.collection.immutable.SortedSet[String] = TreeSet(DOUBLE=>2, ZERO=>0)

上記はより簡潔だと思います

object Ops2 extends Enumeration {

    protected abstract class OpsVal extends Val() {
      def f(a: Int): Int
    }

    val ZERO = new OpsVal { def f(x: Int) = 0 }
    val DOUBLE = new OpsVal { def f(x: Int) = 2 * x }

    implicit def convert(valu: Value) = valu.asInstanceOf[OpsVal]
}
Ops2.ZERO.f(1) // implicit is not needed  //> res2: Int = 0

// implicit is needed
Ops2_3.values map (v => (v, v(1)))        //> res7: scala.collection.immutable.SortedSet[(e.Ops2_3.Value, Int)] = TreeSet
                                              //| ((ZERO,0), (DOUBLE,2))

値ごとに1つのメソッドがあるため、それらを関数に変換できます

object Ops2_3 extends Enumeration {

    protected case class FuncVal(f: Int => Int) extends Val {
        def apply(x: Int) = f(x) // no need to extend Function1 explicitly
    }

    val ZERO = new FuncVal (x => 0)
    val DOUBLE = new FuncVal (x => 2 * x )

    implicit def convert(v: Value) = v.asInstanceOf[FuncVal]

}
Ops2_3.ZERO(1) // implicit is not needed  //> res6: Int = 0

// implicit is needed
Ops2_3.values map (v => (v, v(1)))        //> res7: scala.collection.immutable.SortedSet[(e.Ops2_3.Value, Int)] = TreeSet
                                              //| ((ZERO,0), (DOUBLE,2))

すべての値で共有される関数は、次のように定義できます( arg parser で使用可能)

val args: Array[String] = "-silent -samples 100 -silent ".split(" +").toArray
                                              //> args  : Array[String] = Array(-silent, -samples, 100, -silent)
object Opts extends Enumeration {

    val nopar, silent, samples = new Val() {
        def apply() = args.contains(toString)
        def asInt(default: Int) = { val i = args.indexOf(toString) ;  if (i == -1) default else args(i+1).toInt}
        def asInt: Int = asInt(-1)
        override def toString = "-" + super.toString
    }
}

Opts.nopar()                              //> res0: Boolean = false
Opts.samples.asInt                        //> res1: Int = 100

他のユーザーは、封印されたトレイト+マクロの場合について議論しています Scalaの封印されたトレイトを反復しますか?

0
Val