Groovy2.3.0-beta-1と噂のTraitを試してみた
Groovy2.3.0-beta-1がリリースされましたね。
目玉としてはやはりtraitでしょう。
今までのMetaClassによるメソッドの動的探索パスをいじる系の機能で似たようなことはできましたが、何かと限界(複数スレッドにおける動作の保証とかかなり泥臭い感じだったり、一度動的メソッドを追加すると取り除くのが大変だったり)がありましたが、それらを超えるために静的ソリューションとしてtraitが実装されました*1。
ドキュメントがすごくわかりやすくて、サンプルコードを上から順に読むだけで少なくともどのように使えるかは難なく理解できるでしょう。
最初の方を読んでるとJava8のインタフェース+デフォルトメソッドと同じかな?と誤解しますが、Groovyのtraitはステートフルなのです。つまりプロパティを定義して状態が持てる。もう完全に普通の多重継承です。とうぜんダイアモンド継承などのコンフリクト問題はありますが、ドキュメントにあるとおり、「自前実装最強」「それ以外は登場順の後勝ち」的なルールで明確に決定されますし、特定の実装を優先したい場合も簡単です。まあ、衝突が気になるような名前は避ければいいわけですし。詳しくはドキュメントのこの辺を読みましょう。
GroovyConsoleで軽く試してみましたが、非常にいい感じです。
いわゆるアカデミックな本家の"trait"や、"Scalaのtrait"とどの辺が違うのかあたりは気になるところです。
2014/4/9追記
ASTはこんな感じ。細かい点はおいといてなるほどねーな感じです。
@groovy.transform.Trait
abstract interface public class Flyable extends java.lang.Object {
@org.codehaus.groovy.transform.trait.Traits$Implemented
abstract public java.lang.String fly() {
}
@org.codehaus.groovy.transform.trait.Traits$Implemented
abstract public java.lang.String getName() {
}
@org.codehaus.groovy.transform.trait.Traits$Implemented
abstract public void setName(java.lang.String value) {
}
}
public class Bird implements Flyable, Flyable$Trait$FieldHelper, groovy.lang.GroovyObject extends java.lang.Object {
private java.lang.String Flyable__name
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo
public static transient boolean __$stMC
private transient groovy.lang.MetaClass metaClass
public static long __timeStamp
public static long __timeStamp__239_neverHappen1397005006100
public Bird() {
metaClass = /*BytecodeExpression*/
Flyable$Trait$Helper.$init$(this)
}
public void setName(java.lang.String arg1) {
Flyable$Trait$Helper.setName(this, arg1)
}
public java.lang.String fly() {
return Flyable$Trait$Helper.fly(this)
}
public java.lang.String getName() {
return Flyable$Trait$Helper.getName(this)
}
static {
__timeStamp__239_neverHappen1397005006100 = 0
__timeStamp = 1397005006100
Flyable$Trait$Helper.$static$init$(this)
}
@groovy.transform.CompileStatic
public java.lang.String Flyable__name$get() {
return Flyable__name
}
@groovy.transform.CompileStatic
public void Flyable__name$set(java.lang.String val) {
Flyable__name = val
}
public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) {
return this."$name"(* args )
}
public void this$dist$set$1(java.lang.String name, java.lang.Object value) {
this ."$name" = value
}
public java.lang.Object this$dist$get$1(java.lang.String name) {
return this ."$name"
}
//....
}
public class UFO implements groovy.lang.GroovyObject extends java.lang.Object {
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo
public static transient boolean __$stMC
private transient groovy.lang.MetaClass metaClass
public static long __timeStamp
public static long __timeStamp__239_neverHappen1397005006102
public UFO() {
metaClass = /*BytecodeExpression*/
}
public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) {
return this."$name"(* args )
}
public void this$dist$set$1(java.lang.String name, java.lang.Object value) {
this ."$name" = value
}
public java.lang.Object this$dist$get$1(java.lang.String name) {
return this ."$name"
}
//...
}
abstract public static class Flyable$Trait$Helper implements groovy.lang.GroovyObject extends java.lang.Object {
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo
public static transient boolean __$stMC
private transient groovy.lang.MetaClass metaClass
public Flyable$Trait$Helper() {
metaClass = /*BytecodeExpression*/
}
public static void $init$(Flyable $self) {
(( $self ) as Flyable$Trait$FieldHelper).Flyable__name$set('trait')
}
public static void $static$init$(java.lang.Class<Flyable> $static$self) {
}
public static java.lang.String fly(Flyable $self) {
return "I'm $name and flying!"
}
public static java.lang.String getName(Flyable $self) {
return (( $self ) as Flyable$Trait$FieldHelper).Flyable__name$get()
}
public static void setName(Flyable $self, java.lang.String value) {
(( $self ) as Flyable$Trait$FieldHelper).Flyable__name$set(value)
}
public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) {
return this."$name"(* args )
}
public void this$dist$set$1(java.lang.String name, java.lang.Object value) {
this ."$name" = value
}
public java.lang.Object this$dist$get$1(java.lang.String name) {
return this ."$name"
}
public java.lang.Object methodMissing(java.lang.String name, java.lang.Object args) {
return Flyable."$name"(* args )
}
public void propertyMissing(java.lang.String name, java.lang.Object val) {
Flyable."$name" = val
}
public java.lang.Object propertyMissing(java.lang.String name) {
return Flyable."$name"
}
//...
}
abstract interface public static class Flyable$Trait$FieldHelper extends java.lang.Object {
final public static java.lang.String Flyable__name
abstract public void Flyable__name$set(java.lang.String val) {
}
abstract public java.lang.String Flyable__name$get() {
}
}
*1:実際の経緯がこのとおりかは自信なし