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:実際の経緯がこのとおりかは自信なし