java.lang.Enum.valueOf(classOf[Stuff], name)Hi Alois!
This looks really nice!
There have been some discussions about enums lately: https://groups.google.com/d/topic/scala-language/IcDNzjhxV3E/discussion, https://groups.google.com/d/topic/scala-language/1bzZz14j87o/discussion and https://groups.google.com/d/topic/scala-internals/8RWkccSRBxQ/discussion
Considering the details, I came up with a possible syntax like this ...
object Direction extends scala.Enum {
object North
object East
object South
object West
}
... which would be desugared to this:
final class Direction private(val name: String, ordinal: Int) extends java.lang.Enum[Direction](name, ordinal)
object Direction {
val North = new Direction("North", 0)
val East = new Direction("East", 0)
val South = new Direction("South", 0)
val West = new Direction("West", 0)
private val $VALUES = Array(North, East, South, West)
def values = $VALUES.clone
def valueOf(name: String) =java.lang.Enum.valueOf(classOf[Stuff], name)
}
It uses objects to allow the definition of methods for enum items. I think this would cover all features of Java's enums, as far as I understand it.
From my POV, being compatible with the platform's idea of enums is absolutely crucial. scala.Enumeration is not, and absolutely no one is using it. Emitting JVM-compatible enums probably requires some fixes/changes to the compiler itself (e. g. setting the appropriate flags for the class/the individual enum items when the compiler discovers the Enum type).
I mentioned some of the preliminary work here: https://groups.google.com/forum/?fromgroups=&hl=en#!searchin/scala-internals/enum/scala-internals/-0vjKs3RecE/XawFkdSKqZwJ
Thanks and bye,
Simon
Too verbose imo, I don't see the point of repeating object here.
I never faced case where I wanted to define method on Value instance, but adding methods on Value type definitely make sense and it's why I would like to support like this:
I definitely think that scala.Enumeration not being compatible with JVM Enum isn't the reason people don't use it, as I said on twitter having JVM-compatible would be a nice to have, but I think having specific construct for them in the language spec is a bad thing. It should be done as a lib imo, if we are able to have some feature in macro to allow us to set the needed flag, then fine I would be happy to make them java interopabe.
The main reason folks don't use scala.Enumeration, is imo because they don't give you exhaustive matching (maybe I missed a fix about that?).When one design API that need to be compatible with Java, we end writing some Java code... I think it could be the same with Enum, if you create a java compatible API use java Enum.
Hi Alois,Too verbose imo, I don't see the point of repeating object here.The idea is that you can define methods on the objects if you define them this way ...
object Direction extends scala.Enum with HasFoo {
object North { def foo = 42 }
object East { def foo = 43 }
object South { def foo = 44 }
object West { def foo = 45 }
}
... and it would all be bog-standard Scala syntax.
I never faced case where I wanted to define method on Value instance, but adding methods on Value type definitely make sense and it's why I would like to support like this:
Looks interesting. I preferred my approach because it feels like it needs less tree rewriting, but if it works in your case, I'll be happy too.
I definitely think that scala.Enumeration not being compatible with JVM Enum isn't the reason people don't use it, as I said on twitter having JVM-compatible would be a nice to have, but I think having specific construct for them in the language spec is a bad thing. It should be done as a lib imo, if we are able to have some feature in macro to allow us to set the needed flag, then fine I would be happy to make them java interopabe.
While I partially disagree about the reason s.Enumeration is not used, I think we are absolutely on the same page here. We don't need any specific construct in the language. The change to the backend is purely a check for setting the right flags which we probably need anyway in one way or another, because it is forbidden to extend java.lang.Enum per language spec in Java, but Scala let's you happily do that, which can be very confusing to users.
The main reason folks don't use scala.Enumeration, is imo because they don't give you exhaustive matching (maybe I missed a fix about that?).When one design API that need to be compatible with Java, we end writing some Java code... I think it could be the same with Enum, if you create a java compatible API use java Enum.
Is there any benefit in not being JVM compatible? We loose a ton of interoperability with basically no advantage at all.
We should either get rid of all Enumeration constructs/features/libraries in Scala and point people to Java, or fix it.
I see very limited benefits in having a rewritten Enum if it is still not JVM compatible, especially when it's done via macros where making it compatible would be no big magic anymore.
Bye,
Simon
I see, I may take this path if the syntax I'll try to impl. is not feasible.
Cool, I'm not that much comfortable with this flags stuff, but for sure, I'll definitely investigate how this can be achieved, and fill issues in case something is missing, I'll probably come back to you about that.
Agreed, there is clearly no benefit *not* being JVM compatible!
Regarding the flag business and touching the compiler in general: I think it makes sense to add a check to scalac to prevent people from mistakenly extending java.lang.Enum (like javac does) anyway. So adding an error message in the sense of “It seems you are trying to declare an enum by extending java.lang.Enum. Extend scala.Enum instead.” and making sure that the right flags are set should be “no big deal”.
Thanks,
Simon
Beyond these comments I would like to point to one of my previous
comments that perhaps
enumerated types should be considered as a special case of what is
normally called algebraic
data types (or variant types) in functional programming languages. My
previous comment is included below.
datatype Tree {
object Empty
class Leaf(x:Int)
class Node(left: Tree, right: Tree)
}
1) You can only set flags if you cast to internal API. I believe there's an issue which asks to make that API public.
2) Unfortunately c.introduceTopLevel cannot be used to generate companions, because its argument is put into a virtual source file, which is deemed to be different from the source file, which hosts a manually written companion, which triggers a compilation errors.
> I've just pushed to github my recent experimentation about implementing
> Enumeration using type macro:
> https://github.com/aloiscochard/enum-paradise
>
> It's still very WIP, for the moment you can't define custom Value trait or
> even change values name, but it should give you an idea.
>
> You will find in the test the syntax I want to support (what is not
> currently implemented is in comment, still not sure about the syntax yet):
> https://github.com/aloiscochard/enum-paradise/blob/master/core/src/main/scala/test.scala
I am not sure I like the quotes in:
object Days extends Enum('Monday, 'Tuesday, 'Wednesday, 'Thursday, 'Friday)
and the need to write Days.Value in:
type X = Days.Value
def xs: Seq[Days.Value] = Days.values
It is also not quite clean that there are quotes above but not here:
In fact I don't think we need to generate a companion, but just introduce a sealed trait named according to enum name and adding a prefix, I'm pretty sure this is feasible using introduceTopLevel from what I seen in your type provider example.
To enable it on a syntactic level, I'm considering something like:
class Days(val inGerman: String) /* 1. */ extends Enum with HasName /* 4. */ (
Monday("Montag"),
Tuesday("Dienstag"),
Wednesday("Mittwoch"),
Thursday("Donnerstag"),
Friday("Freitag"),
Saturday("Samstag") { override def workingDay: Boolean = false }, // 3.
Sunday("Sonntag") { override def workingDay: Boolean = false }
) {
def abbreviation = name take 3 // 2.
def workingDay: Boolean = true // 3.
}
trait HasName { def name: String }
Additional rules would be:
I think it would be possible to avoid the restriction regarding companions by not emitting a companion object at all and instead setting the proper static modifiers for values/methods (too bad that the @static annotation didn't make it into 2.10).
I'm not sure whether type macros would be powerful enough to guarantee all the constraints related to the methods defined in the class body. I tried to use multiple argument lists for Enum, like defining Enum(values: _*)(definitions: _*) and using it like Enum(/* values */) { /* definitions */ } to get more control over it, but that doesn't seem to be supported.
Alois, Eugene, what do you think?
Thanks and bye,
Simon
Where is "name" bound in Days?
Eugene, could you give me a few hints into the right direction?
Thanks a lot!
Simon
1) Sorry I haven't been following the thread for quite a while. Where would you like to put the STATIC flag?
val STATIC = 1 << 23val PARAMACCESSOR = 1 << 29def setFlag(symbol: Symbol, flag: Long) {val compilerSymbol = symbol.asInstanceOf[scala.tools.nsc.Global#Symbol]compilerSymbol.setFlag(flag)}def printFlags(symbol: Symbol) {println(symbol.asInstanceOf[scala.tools.nsc.Global#Symbol].flagString)}
2) I don't think it's a bug - it's just a timing issue, because type macros expand quite early in the typechecking pipeline.
3) c.reifyRuntimeClass should do the trick
1) I don't think that will work. From what I can see, STATIC only has effect on members of objects.
3) [...] the right approach here would probably involve generating a stub, which would expand into j.l.Class later on, when the synthesized Template is typechecked. That's exactly what you did.
4a) So it did work when you explicitly specified Unit as the return type without appending Literal(Constant(())) to the block?
4b) I don't think so. That's because type macros consume a typename slot in their owner's namespace. Just as you cannot define a type alias and a class with the same name, you can't define an homonymous trait alongside a type macro.
--
1) Unfortunately there's none. Not until we have the machinery to changed arbitrary classes (something which is attributed to the macro annotation milestone).
4a) The right thing is to append (), because UnCurry seems to expect a Block and, as far as I can guess, if you omit the (), the rhs of the ctor is treated as a plain expr.
4b) Everything works when I change New(...) to Select(New(...), nme.CONSTRUCTOR) in enumInstance. That's very misleading, I agree, but at least I can say that this was mentioned in the docs. I understand though that's not an excuse for exposing a quirky API.
--
--
4b) You got me scared for a moment, but I just verified that showRaw actually prints Select(New(...), nme.CONSTRUCTOR). Phew - at least this works as intended.
Wow there's something really weird going on. First of all, namers explicitly set tpts of constructors to their enclosing class. Secondly, typers typecheck constructor bodies against WildcardType, not against those tpts. Probably, for now we should avoid touching tpts of constructors.
... or ...