読者です 読者をやめる 読者になる 読者になる

Groovy2.3.0-beta-1と噂のTraitを試してみた

Groovy2.3.0-beta-1がリリースされましたね。

目玉としてはやはりtraitでしょう。

今までのMetaClassによるメソッドの動的探索パスをいじる系の機能で似たようなことはできましたが、何かと限界(複数スレッドにおける動作の保証とかかなり泥臭い感じだったり、一度動的メソッドを追加すると取り除くのが大変だったり)がありましたが、それらを超えるために静的ソリューションとしてtraitが実装されました*1

ドキュメントがすごくわかりやすくて、サンプルコードを上から順に読むだけで少なくともどのように使えるかは難なく理解できるでしょう。

最初の方を読んでるとJava8のインタフェース+デフォルトメソッドと同じかな?と誤解しますが、Groovyのtraitはステートフルなのです。つまりプロパティを定義して状態が持てる。もう完全に普通の多重継承です。とうぜんダイアモンド継承などのコンフリクト問題はありますが、ドキュメントにあるとおり、「自前実装最強」「それ以外は登場順の後勝ち」的なルールで明確に決定されますし、特定の実装を優先したい場合も簡単です。まあ、衝突が気になるような名前は避ければいいわけですし。詳しくはドキュメントのこの辺を読みましょう。

GroovyConsoleで軽く試してみましたが、非常にいい感じです。

gist10127510

いわゆるアカデミックな本家の"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:実際の経緯がこのとおりかは自信なし