Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subclass Enumeration to allow case classes etc. to function as enums #2111

Closed
scabug opened this issue Jun 30, 2009 · 4 comments
Closed

Subclass Enumeration to allow case classes etc. to function as enums #2111

scabug opened this issue Jun 30, 2009 · 4 comments
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Jun 30, 2009

As was discussed on the list, Enumeration lacks the advantages of being object oriented, while case classes/objects lack the advantages of being enumeratable. A simple subclass of Enumeration, which I would like to contribute to the standard library, solves this problem. The idea of using reflection to solve the problem of lazy object intialization comes from lift's MetaMapper.

This only works for

val enums = new CaseEnum { ... }

or

class Enums extends CaseEnum { ... }
object Enums extends Enums

but for

object Enums extends CaseEnum { ... }

you have to reference the members in the body,
e.g.

A; B; C

Example usage:

val Colors = new CaseEnum {
  sealed abstract class Color extends Case
  object Red extends Color
  object Blue extends Color
}
/*OR*/ object Colors extends CaseEnum {
  sealed abstract class Color extends Case
  object Red extends Color
  object Blue extends Color
  (Red,Blue)
}
Colors.foreach {c=> println("Value "+c+" is mapped to "+Colors.cases(c))}
/* should print
Red is mapped to Red
Blue is mapped to Blue
*/

Note that members do not actually have to be case classes or case objects.
Also, I haven't tested it on 2.8. Is there a significant difference in Enumeration?

Here is my source code:

class CaseEnum extends Enumeration {
  private val _cases = new scala.collection.mutable.HashMap[Value, Case]
  trait Case {
    private val name = {
      val name = getClass.getName
      if(name.endsWith("$$"))
        name.substring(name.lastIndexOf("$$", name.length-2)+1, name.length-1)
      else
          name.substring(name.lastIndexOf("$$")+1)
    }
    val value = Value(name)
    _cases += (value -> this)
  }
  
  def cases(v: Value) = _cases(v)
  for {
    m <- getClass.getMethods
    if m.getReturnType.getName.endsWith("$$"+m.getName+"$$") &&
      m.getParameterTypes.length==0 &&
      classOf[Case].isAssignableFrom(m.getReturnType)
  } {
    m.invoke(this)
  }
}
@scabug
Copy link
Author

scabug commented Jun 30, 2009

Imported From: https://issues.scala-lang.org/browse/SI-2111?orig=1
Reporter: Naftoli Gugenheim (naftoligug)
See #5211

@scabug
Copy link
Author

scabug commented Oct 21, 2009

@phaller said:
In your usage example, the members of the CaseEnum are objects. If I change them to be case classes, I have to create instances of them first, before they are visible as members of the CaseEnum:

  val Colors = new CaseEnum {
    sealed abstract class Color extends Case
    case class Red extends Color
    case class Blue extends Color
    Red()
    Blue()
  }

  Colors.foreach {c=> println("Value "+c+" is mapped to "+Colors.cases(c))}

With objects this is not necessary, since you are referencing them using reflection in your for-comprehension.

Otherwise, I'll bring your CaseEnum up for discussion in the next Scala meeting.

@scabug
Copy link
Author

scabug commented Oct 23, 2009

Naftoli Gugenheim (naftoligug) said:
Why would enum members not be singletons?
Anyway, here is an improved version (see below).

class CaseEnum extends Enumeration {
  type CASE <: Case
  private val _cases = new scala.collection.mutable.HashMap[Int, CASE]
  trait Case { this: CASE =>
    private lazy val _id = nextId
    private lazy val _name = {
      val name = getClass.getName
      if(name.endsWith("$$"))
        name.substring(name.lastIndexOf("$$", name.length-2)+1, name.length-1)
      else
        name.substring(name.lastIndexOf("$$") + 1)
    }
    def name = _name
    def id = _id
    lazy val value = Value(id, name)
    private[CaseEnum] def init = {
      _cases += (value.id -> this)
    }
  }
  
  def cases(v: Value) = _cases(v.id)
  def cases(id: Int) = _cases(id)
  def cases(name: String) = _cases.values.find(_.name==name).get
  import scala.util.Sorting._
  def cases = stableSort(_cases.values.collect, (c:CASE)=>c.id)
  import java.lang.reflect.Method
  for {
    m <- stableSort(getClass.getMethods.toSeq, (a:Method,b:Method)=>(a.getName < b.getName))
    if m.getReturnType.getName.endsWith("$$"+m.getName+"$$") &&
      m.getParameterTypes.length==0 &&
      classOf[Case].isAssignableFrom(m.getReturnType)
  } {
    m.invoke(this).asInstanceOf[Case].init
  }
}

This version has a small improvement to a basic problem; maybe you have a better solution. Previously, the members were essentially assigned ids in the order they were returned by reflection. This order is not stable between rebuilds of the program. I therefore added code to sort them alphabetically. This of course does not solve the problem of adding elements later. Support was also added for setting the id. For example:

class Priorities extends CaseEnum {
  type CASE = Priority
  case class Priority(override val id: Int) extends Case
  case object Routine extends Priority(0)
  case object Urgent extends Priority(1)
  case object Emergency extends Priority(2)
}

More methods were added for looking up members, Also, a type member was added to solve a basic problem: CaseEnum needs to know the exact type it's supposed to return from cases().
I may have omitted any other improvements; these modifications were made quite a while back, and I didn't get around to posting them.
Thanks.

@scabug
Copy link
Author

scabug commented Oct 27, 2009

@phaller said:
We discussed the ticket in today's Scala meeting, and we came to the conclusion that obtaining names for the values of an enumeration automatically (that is, through reflection) is a reasonable thing to do, since otherwise the order of values and explicitly provided names must be kept in sync by the programmer--a source of bugs.

As for the other parts of the proposal, we decided not to allow objects as values of an enumeration, since this would create several classes per enumeration. We think that Enumeration should provide what it does specifically with the goal of avoiding to create an additional class per enumeration value--one of the advantages of an enumeration.

The improvements we agreed on in the meeting are implemented in r19311 (trunk) with a test in files/run/t2111.scala.

@scabug scabug closed this as completed May 18, 2011
@scabug scabug added this to the 2.8.0 milestone Apr 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants