uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

オブジェクト指向だけじゃない開発宣言

Manifesto for Not Only Object-Oriented Developmentオブジェクト指向だけじゃない開発宣言、というのが出てましたのでごにょと訳してみました*1

オブジェクト指向だけじゃない開発宣言

私たちは、ソフトウェア開発の実践
あるいは実践を手助けをする活動を通じて、
よりよい開発方法を見つけだそうとしている。
この活動を通して、私たちは以下の価値に至った。


クラスよりも、関数と型を、
可変性よりも、純粋性を、
継承よりも、コンポジションを、
メソッドディスパッチよりも、高階関数を、
nullよりも、Optionを、


価値とする。すなわち、左記のことがらに価値があることを認めながらも(但しnullは除くが)、私たちは右記のことがらにより価値をおく。

内容は、「今どきのオブジェクト指向開発宣言」と名乗ってもあんまり違和感ないっす。
ただし、null、おめーは別だ。

追加希望
正格評価よりも、非正格評価を、
チューリングマシンではなく、SKコンビネータを、

追記:この翻訳が本家の日本語訳に採用されました。

*1:元ネタがアジャイル開発宣言のパロなので、訳も借用させてもらいました。

@BaseScriptアノテーションはscriptをインクルードするのに使えます

Groovy 2.2以降、@BaseScriptアノテーションというのが導入され、Scriptの基底クラスを指定することができる*1ようになりました。

これは主目的としては、専用目的の便利機能追加・DSLの作成だと思いますが、単にscriptをインクルードするのにも使えます。

例えば、以下の「OtherScript.groovy」をクラスパスの通ったところか、あるいはカレントディレクトリに配置しておきます。

// OtherScript.groovy
def methodOfOtherScript() {
    "i'm methodOfOtherScript1"
}

def methodOfOtherScript1() {
    "i'm methodOfOtherScript2"
}

println "this is otherScript!"
a=3

これを呼び出す、mainscript.groovyでは以下のように@BaseScriptアノテーションを指定します。

import groovy.transform.BaseScript
@BaseScript OtherScript _

super.run()
println methodOfOtherScript1()
println methodOfOtherScript2()
println a

すると以下のとおり。

$ groovy mainscript.groovy
this is otherScript!
i'm methodOfOtherScript1
i'm methodOfOtherScript2
3

バインディング変数も引き継がれます。

main側のスクリプトを起点には探索してくれないようですが*2、OtherScriptのメソッドが修飾無しでmainscript.groovyから呼び出せます。ちなみに呼びだされるクラスは、大文字で始まらないと認識してくれません。つまり「otherscript.groovy」では駄目でした。

これ、前から欲しいと思ってたんだよね。呼ばれる側をクラス定義+staticメソッドにしてstatic importするとか、でも元のスクリプトスクリプトとして実行できなくなるし、それならスクリプトのままならnewしてインスタンスメソッドとして呼ぶとか、でもバインディング変数の引き継ぎとか駄目だし、mixinとかではあまり上手くいかないし*3

でもこの方法でも、1つのスクリプトで複数のscriptをimportすることは、できませんね。
ここは、ScriptがTraitだったら良かったのにな*4、ってところでしょう。

(追記2014/4/26)
バインディング変数は引き継げませんが、@Delegate+@Fieldでこんなのも可能だと今気づいた。

import groovy.transform.*

@Field @Delegate OtherScript a = new OtherScript()
@Field @Delegate SomeScript b = new SomeScript()

println methodOfOtherScript1()
println methodOfOtherScript2()
println methodOfSomeScript1()
println methodOfSomeScript2()

これなら複数のスクリプトもincludeできますや。
こっちの方がいいかも^^;

*1:従来から、コマンドラインオプションやコンパイラコンフィグレーションでは指定できたが、スクリプト一つで完結する形で指定できるようになった。

*2:動的に明示的にクラスパスに追加すれば可能と思うが

*3:2.2でやはり導入された@DelegatingScriptを@BaseScriptに指定してやれ!と思ったがNPEで不可。バグかなんらかの制限んか。

*4:実際には@BaseScriptが複数親指定を許さないと駄目だから、単にTraitになってもできない。@BaseScript側の対応も必要。

Rustを30分で紹介する(訳)

以下は30 minute introduction to Rustという文書(現在ではオフィシャルドキュメントの一部)の翻訳メモです。C/C++の代替に(将来的に?)なり得る言語として興味を持っています。Heartbleed問題について、ソースが汚いとか、レビュー体制がどうのとか、そもそもSSLの仕様的にどうなの、なんていう議論もあるんでしょうが、人間が間違いを犯す可能性があるということを前提とするなら、問うべき根源的な問題の一つとして、memcpyの使用ミスがあったときに、パスワードのような重要情報を含み得るメモリ領域内全内容を原理的には読まれ得る、っていう根本アーキテクチャを許しておくこと、すなわち、原理主義的にはC言語のごときをセキュアな用途に採用するのが悪い、もしくは採用せざるを得ない状況になっているのが悪いというのが一つとしてあるんじゃねーの、とは思います(誰もが思うでしょうが)。なので、Heartbleedが社会問題化するに伴ない、今後余波で流行る・流行る圧力が増すんではないでしょうか。goやSafeD、などと比べてどんなもんでしょう。



STEVE KLABNIK

最近私は、Rustの包括的なドキュメント化を提案しました。特に、短かくてシンプルな「Rustについて聞いたことがあるけれど、自分にとって適切かどうかを知りたい人向けに紹介する文章」が重要だと思っています。こちらのプレゼンテーションを見て、そのような紹介の基礎となると思いました。この文書を、RFCとしてみてください。コメントはrust-devメーリングリストTwitterにてお願いします。

Rustは、コンパイル時に正しさを保証することに強く主眼を置いた、システムプログラミング言語です。C++やD、Cycloneといった他のシステム言語のアイデアを、強い保証と記憶領域ライフサイクルの明示的制御で改良したものです。Rustでは、強力な記憶領域の保証によって、他の言語よりも容易に正確な並行処理コードを書くことができます。

ここでは、Rustにおける最も重要な概念「オーナーシップ」と、それがプログラマにとっての高難易度タスク「並行処理」の記述にどう影響を及ぼすか、について説明してみます。

オーナーシップ

「オーナーシップ」は、Rustにおいて、主要な、興味深い、もっともユニークな機能の一つです。オーナーシップは、記憶領域の各部分が、プログラムのどの部分によって変更されても良いかを指定します。まずはとあるC++コードを見てみましょう。

int *dangling(void)
{
    int i = 1234;
    return &i;
}

int add_one(void)
{
    int *num = dangling();
    return *num + 1;
}

この関数danglingは整数をスタック上にアロケートして、変数iに格納し、iへの参照を返しますが、一つだけ問題があります。スタック上の記憶領域は関数からリターンすると無効になってしまうということです。これが意味するのは、add_oneの二行目では、numはゴミの値を指しており、望む結果は得られないということです。小さな例ですが、これはしばしば実際にC++のコードで起こります。同様な問題が、malloc(もしくはnew)で割り当てられて解放された後のメモリ領域へのポインタに対して何か処理しようとしたときにも生じます。現代的なC++は、RAIIをコンストラクタ・デストラクタで使用しますが、それでも結局は同じです。この問題は「ダングリングポインタ」と呼ばれるのですが、この問題を持ったRustコードを書くことはできません。やってみましょう。

fn dangling() -> &int {
    let i = 1234;
    return &i;
}

fn add_one() -> int {
    let num = dangling();
    return *num + 1;
}

このプログラムをコンパイルしようとすると、以下のような興味深い(そして長い)エラーメッセージが表示されます。

temp.rs:3:11: 3:13 error: borrowed value does not live long enough
temp.rs:3     return &i;

temp.rs:1:22: 4:1 note: borrowed pointer must be valid for the anonymous lifetime #1 defined on the block at 1:22...
temp.rs:1 fn dangling() -> &int {
temp.rs:2     let i = 1234;
temp.rs:3     return &i;
temp.rs:4 }

temp.rs:1:22: 4:1 note: ...but borrowed value is only valid for the block at 1:22
temp.rs:1 fn dangling() -> &int {      
temp.rs:2     let i = 1234;            
temp.rs:3     return &i;               
temp.rs:4  }                            
error: aborting due to previous error

このエラーメッセージを完全に理解するためには、何かを「所有する(own)」という概念について説明する必要があります。とりあえずここでは、Rustがダングリングポインタのコードを書くことを許さない、ということを知って、それから「オーナーシップ(ownership,所有権)」について理解したあとでこのコードについて再度戻って来て考えることにしましょう。

プログラミングについてはちょっとだけ忘れて、「本」について考えてみましょう。私は物理的な本を読むことが好きですが、時々本当に気に入った本は、友達に「この本は読むべきだ!!」と言うことがあります。私が私の本を読んでいる間、私がこの本を「所有」しています。この本を誰かにしばらく貸したとき、彼は私から本を「借り」ます。もしあなたが本を借りたならば、その本はある期間だけあなたの物になります。そして私に本を返したら、わたしは本を再び「所有」することになります。いいですよね。

このような考え方が、Rustコードではまさに適用されているのです。あるコードはメモリへの特定のポインタを「所有」します。そのコードは、ポインタの唯一の所有者です。メモリをその他のコードにしばらく貸すこともできます。コードは「ライフタイム」と呼ぶある期間だけ、そのメモリを「借りる」のです。

これだけです。難しくないですよね。さて先のエラーメッセージに戻ってみましょう。エラーメッセージはこう言っています「借りた値は十分長くは生存しない(borrowed value does not live long enough)」。ここではRustの借用ポインタ(Borrowed Pointer)「&」を使ってiという変数を貸しだそうとしています。しかしRustは変数は関数からリターンすると無効になることを知っているので、こう表示します。「借用ポインタは無名ライフタイム#1の期間有効でなければならないが、借用値はこのブロックでのみ有効である」。すばらしい!

これはスタックメモリの良い例ですが、ヒープについてはどうでしょう。Rustは二番目の種類のポインタ「ユニーク」ポインタを持っています。(訳注: Rustの仕様がかわり、~は最新のRustではシンタックスとしては削除された。その代わりに、型としてはBox<T>トレイト、Boxを作成するにはBox::new()を使用する*1。この機能はOwned BoxもしくはOwnning Pointer、もしくは単にBoxなどと呼ぶことが多いようだ。)

fn dangling() -> ~int {
    let i = ~1234;
    return i;
}

fn add_one() -> int {
    let num = dangling();
    return *num + 1;
}

このコードは成功裏にコンパイルできます。スタックに割り当てられる1234の代りに、その値への所有ポインタを代りに使っていることに注意してください。大ざっぱには以下のように対応します。

// rust
let i = ~1234;
// C++
int *i = new int;
*i = 1234;

Rustはそのデータ型の格納のために必要な正しい量のメモリを割り当て、指定した値が設定されます。これが意味するのは、初期化されていないメモリが割り当てられることはないということです。Rustはnullの概念を持っていません。やったー!!! RustとC++では一つ違いがあります。Rustコンパイラはiのライフタイムを知っているので、それが無効になったときに対応するfree呼び出しをC++のデストラクタのように挿入してくれるのです。保守を自分でやらなくても、手動でヒープメモリを割り当てる利点を全て得ることができるのです。さらに、ランタイムのオーバーヘッドはありません。あなたはC++で正しく書いたのと(基本的には)正確に同じコードを得ることができ、間違ったバージョンを書くことはできません。ありがとうコンパイラ!

所有権とライフタイムというものが、厳しくない言語で書いた場合に通常危険になってしまうコードを避けるために有用である例を1つを見てきました。さて次に、もう1つの話題を示しましょう。並行性です。

並行処理

並行処理は、ソフトウェアの世界では今やびっくりするほど熱い話題です。計算機科学の研究では、従来から常に興味深い領域でしたが、インターネットの爆発的な利用拡大に伴なって、サービスあたりの利用者数をさらに増やすことが求められています。でも並行処理コードにはかなり大きな不利益があります。非決定性です。並行処理コードを書くための異なるアプローチはいくつかあるわけですが、ここでは所有権とライフタイムの概念を使ったRustの記法が、正しい並行処理を書くのをどう助けてくれるかを見てみましょう。

最初に、単純な並行処理を実行するRustのサンプルコードを示します。Rustは「タスク」を起動(spin up)することを許します。タスクは軽量なグリーンスレッドです。これらのタスクは共有メモリをもたず、チャネルを通じて以下のように通信します。

fn main() {
    let numbers = [1,2,3];

    let (port, chan)  = Chan::new();
    chan.send(numbers);

    do spawn {
        let numbers = port.recv();
        println!("{:d}", numbers[0]);
    }
}

この例では、数値のvectorを作成します。われわれはまず、Chanをnewします。ChanというのはRustがチャンネルを実装するパッケージ名です。これは2つの異なるチャンネル端である「channel」と「port」を返します。channel端にデータを送信し、port端からデータを取り出します。spawn関数は新しいタスクを起動(spin up)します。上のコードで判るように、新しいタスクの内側でport.recv()を呼んでおきます(recvは'receive'(受信)の短縮)。新しいタスクの外側からvectorを渡してchan.send()を呼びます。そして内側のタスクでは受け取ったvectorの最初の要素を表示します。

これはRustがvectorをチャンネルを経由して送信する際にコピーするので動作します。このように、もしvectorがミュータブルであったとしても、競合条件にはなりません。しかしながら、大量のタスクを作ったんら、あるいはデータが巨大な場合、タスクごとにコピーしてしまうとメモリを無駄に使ってしまいます。そうすることの利点もありません。

Arcを見てみましょう。Arcは「atomically reference counted(自動的な参照カウント)」の頭字語であり、不変データを複数のタスク間で共有する方法です。以下、コード例です。

extern mod extra;
use extra::arc::Arc;

fn main() {
    let numbers = [1,2,3];

    let numbers_arc = Arc::new(numbers);

    for num in range(0, 3) {
        let (port, chan)  = Chan::new();
        chan.send(numbers_arc.clone());

        do spawn {
            let local_arc = port.recv();
            let task_numbers = local_arc.get();
            println!("{:d}", task_numbers[num]);
        }
    }
}

このコードは先ほどのコードととても似ていますが、3回ループしているところが違います。3つのタスクを作成し、それらにArcを送ります。Arc::newは新しいArcを作成し、.clone()は新しいそのArcへのリファレンスを作ります。.get()はArcから値を取り出します。まとめると、それぞれのタスク用に作成した新しいリファレンスをチャンネルに送信し、リファレンスを使って数字を表示します。この場合vectorはコピーされません。

Arcは変更不可データを扱うのには素晴しいですが、変更可能データについてはどうでしょう? 可変状態の共有は、並行処理プログラマにとって悩みのたねです。共有される可変状態を守るのにmutexが使えますが、mutexを取得するのを忘れたら不幸が起き得るでしょう。

Rustは可変状態を共有するためのツールであるRWArcを提供します。こちらは内容変更を許すArcの変種です。以下を確認してみてください。

extern mod extra;
use extra::arc::RWArc;

fn main() {
    let numbers = [1,2,3];

    let numbers_arc = RWArc::new(numbers);

    for num in range(0, 3) {
        let (port, chan)  = Chan::new();
        chan.send(numbers_arc.clone());

        do spawn {
            let local_arc = port.recv();

            local_arc.write(|nums| {
                nums[num] += 1
            });

            local_arc.read(|nums| {
                println!("{:d}", nums[num]);
            })
        }
    }
}

この場合RWArcパッケージで「読み書き可能Arc」を取得します。「読み書き可能Arc」のAPIはArcとは少しだけ異なり、readとwriteがそれぞれデータを読み書きします。これらの関数はクロージャを引数として取ります。Arcに対するwriteはmutexを獲得し、クロージャにデータを渡します。クロージャーの実行後、mutexを解放します。

こうなっているので、mutexを獲得するのを忘れて状態を変更することはできません。可変状態の共有禁止と同じ安全性を保ちながら、可変状態を共有による効率性も得ることができます。

でも、ちょっと待ってくださいよ。そんなことが可能なんでしたっけ? 可変状態を許しつつ禁止することなんかできないはずです。いったいどうなってるんだ?

unsafeについての脚注

Rust言語は共有可変状態を許しませんが、それをするコード例を示しました。どうしてこんなことが可能なんでしょうか? 答えはunsafeです。

Rustコンパイラはとても賢く、失敗しがちな場面で守ってはくれるのですが、人工知能ではありません。私たちはコンパイラより賢いので(時々ですが)、この安全を優先する動作を上書きする必要があるのです。この目的のために、Rustにはunsafeキーワードがあります。unsafeブロック中ではRustは安全性チェックをオフにします。異常があったときは、注意深くコードを監査する必要があるのはunsafeの内側のみであり、プログラム全体ではありません。

もし主要な目的の一つが安全性であるなら、その安全性をオフにできるのはなぜでしょうか? 主には3つの理由があります。(1)例えばFFIを使ったCライブラリなどの外部コードとのインターフェース、(2)パフォーマンスのため(不可避なケースがある)、(3)通常は安全ではない操作の上に安全な抽象を提供するケース、です。Arcsは最後の目的達成のための例になっています。複数の参照をArcで分配することができますが、そのデータを変更不可であると確かに知ってるからで、共有しても安全です。複数のRWArcのリファレンスを分配することもできますが、なぜならそのデータがmutexでラップされることを知っているからであり、共有しても安全です。しかし、Rustコンパイラはこのような選択をしたことを知ることはできません。Arcの内部実装では、(通常は)危険なことをするためにunsafeブロックを使用しています。しかし、我々が公開するのは安全なインターフェースなので、Arcsの使用者は誤まった使用をすることができません。

並列処理プログラミングを困難にするようなミスを発生不可能にし、さらにC++のような言語の効率性も得るために、Rustの型システムがやっているのはこういうことです。

どうもありがとう、みんな

このRustについてのちょっとした情報で「おお、Rustはおれにとって良い言語だわ」と思ってくれたら有り難いです。もしそうなら、Rustの文法と概念についての説明についてのチュートリアルをチェックしてみて下さい。


訳は以上。以下、他の情報

*1:box という記法もあるが現時点ではunstable featureである

Groovyを勉強したい人に贈る、astprintコマンド

今ごろ言うな、って話かもしれませんが、自分で2年ほど前にこっそり作っていて最高に便利だと思っているコマンド「astprint」を紹介します。

astprintの内容は以下のようにシェルスクリプトです。

#!/bin/sh

groovy -e 'groovy.inspect.swingui.AstNodeToScriptAdapter.main(args)' $*

やってることは"こちら"で紹介したAstNodeToScriptAdapterを呼んでるだけです。以下ようにコンパイルの途中経過が標準出力にテキストで出力されるので簡単に見れます。本当便利です。

% cat hoge.groovy
cat hoge.groovy
trait FileNameIsHogeHoge {
    @groovy.transform.ForceOverride
    String getPath() {
        return "hogehoge"
    }
}

class MyFile extends File implements FileNameIsHogeHoge {
    MyFile(String fileName) {
        super(fileName)
    }
}

f = new MyFile("/tmp/file.txt")
assert f.getPath() == "hogehoge"

% astprint hoge.groovy 4
astprint hoge.groovy 4
f = new MyFile('/tmp/file.txt')
assert f.getPath() == 'hogehoge' : null
public class script1397699411969 extends groovy.lang.Script { 

    public script1397699411969() {
    }

    public script1397699411969(groovy.lang.Binding context) {
        super.setBinding(context)
    }

    public static void main(java.lang.String[] args) {
        org.codehaus.groovy.runtime.InvokerHelper.runScript(script1397699411969, args)
    }

    public java.lang.Object run() {
        f = new MyFile('/tmp/file.txt')
        assert f.getPath() == 'hogehoge' : null
    }

}
@groovy.transform.Trait
abstract interface public class FileNameIsHogeHoge extends java.lang.Object { 

    @groovy.transform.ForceOverride
    @org.codehaus.groovy.transform.trait.Traits$Implemented
    abstract public java.lang.String getPath() {
    }

}
public class MyFile implements FileNameIsHogeHoge extends java.io.File { 

    public MyFile(java.lang.String fileName) {
        super(fileName)
    }

}
abstract public static class FileNameIsHogeHoge$Trait$Helper extends java.lang.Object { 

    public static void $init$(FileNameIsHogeHoge $self) {
    }

    public static void $static$init$(java.lang.Class<FileNameIsHogeHoge> $static$self) {
    }

    @groovy.transform.ForceOverride
    public static java.lang.String getPath(FileNameIsHogeHoge $self) {
        return 'hogehoge'
    }

}

これで見るとtraitは@groovy.transform.Trait指定になってるんですねー。4はコンパイルのフェイズで、以下の意味があります。

数字 シンボル 説明
1 INITIALIZATION ファイルを開いたり
2 PARSING 字句・構文解析ANTLRのAST構築
3 CONVERSION CSTからASTへの変換
4 SEMANTIC_ANALYSIS ASTの意味解析と解明
5 CANONICALIZATION ASTの補完
6 INSTRUCTION_SELECTION クラス生成(フェーズ1)
7 CLASS_GENERATION クラス生成(フェーズ2)
8 OUTPUT クラスをファイルに出力
9 FINALIZATION 後始末
9 ALL 後始末まですべて実行

groovyConsoleでも同じ内容が表示できますが、コマンドラインからできるのも大変便利です。
traitについては7ぐらいにするともっと展開された結果になりますが、バイトコード生成レベルではなく、かなりソースコードに近い側で展開処理してるようで参考になます。

Groovy 2.3のtraitをもうちょっと調べてみるついでにScalaのtraitを理解する


(訂正2014/4/17)以下の記事ではGroovy 2.3のtaritはスタッカブルには使えない、と書いておりますが、以下によれば次のベータ(Groovy 2.3-beta-3かな)ではスタッカブルトレイトが利用可能になるようです。

先の記事ではGroovy 2.3のtraitをかるく紹介してみました。

Scalaの比較も試みましたが、なにしろScalaのtraitをあんまり知らないので、「Groovyのtraitにある機能に限っての、Scalaの対応物と比較」という比較になっておりました。その意味で、「Scalaにしか無い機能」というのは比較の眼中になかったのです。

ということで「Scalaスケーラブルプログラミング」のtraitの章を読んでtraitを理解したつもりになったので、本格的な比較をしてみます。

Scalaスケーラブルプログラミング第2版
Martin Odersky Lex Spoon Bill Venners
インプレスジャパン
売り上げランキング: 29,544

Scalaのtraitは単なる多重継承じゃないよ(という主張)

上記の本のScalaのtraitのとこを読んでると、「単なる多重継承じゃないよ」という主張がひしひしと伝わります。その根拠の一つは、traitでは「super」の意味が拡張されているということのようです。

superの意味が拡張されなければならない理由はごく単純で、素朴な多重継承では「super」が示す親クラスは一つではないからです。多重継承というのは親が複数あるということですからね。かといってsuperを利用不可にしてしまうと、単一継承でのsuperの使い道であるところの「super経由で親のインスタンスを取り出して、superのメソッドを呼び出し、その前後に処理を追加する」という定番のテクニックが使えなくなってしまいます。

Scalaでは、この問題を発展的に解決するために、トレイトの多重継承によって形成されるDAGの要素であるトレイトを、特定のルールに従って線形に並べ直します。線形化すれば、単一継承の様にsuperいっちょうで親を次々とたどっていけるようになります。その順序は良くわかんないものですが、最後に根っこに到達するDAG上の一筆書の経路です。

「発展的解決」の意味は、withの指定順序を替えると処理順序が変わるので、動作のバリエーションが作れるということです。このようなトレイトの用法をスタッカブルトレイトと呼ぶようです。もっとも、試験しなければならないパターンの組み合わせ爆発を考えると、容易に悪夢になり得るとも思うわけですが。

ポイントとして、この線形化経路、すなわち任意のトレイトにおいてsuperが何を指すかは、一つのトレイトを見ただけでは決定できません。あるトレイトが、どうwithで指定されたか(Groovyで言うtraitの複数implements)、によって、順序が変わっていくからです。

trait C extends A with B;

のときBのsuperは(たぶん)Aですが、

trait C extends B with A;

のときのBのsuperは(確か)Cになるでしょう*1。これは、new ... with構文で生成される内部クラスを含め、withを使用してトレイトをミックスインしたクラス定義ごとに、線形化された順序になるようにsuperのチェインを構成・初期化するようなコードないしデータ構造が用意されるのでしょう。

クラス定義に指定されたwithのパターンが、この線形順序をコンパイル時に決定します。superを辿っていくと、この線形化順序で「多重継承上のすべての親」を辿り尽せることが保証されます。superが実際に示す先のインスタンスの型は、実行時にならないと決定されませんので、その意味でsuperは動的です(ただ動的といっても、JavaメソッドでList型の引数に渡ってくるインスタンスArrayListなのかLinkedListなのかあるいはそれを継承した他のクラスのインスタンスなのか動的に決まる、のと同じ意味で動的です。「superの型」が抽象クラスかインターフェースになるということです)。

方やGroovyでは

Groovyでは、(トレイトを明示指定しない)superが利用できないので、必要なトレイトを必要な順序で組み替えてsuperを頼りに処理を組み合せるスタッカブルトレイト的に利用することはできませんヨー(2014/04/17追記。冒頭に書いたように次のβでは可能になるようです。ヤター。)。あと、Groovyでは親トレイトの探索順序が違います。例えsuperの参照ができないとしても、「祖父と伯父」の両方で定義されたメソッドのどっちが優先されるか、といった疑問に答えるためには、線形化された順序が定まっているはずです。調べると、Groovyのトレイトは単純な深さ優先探索です。これに対してScalaはとんでもなくわからないルールで決まります。

ちなみにGroovyの親トレイトの探索順序は以下で調べました。

trait A1 {/* String whoami(){"A1"}*/ }
trait B1 implements A1 { /*String whoami(){"B1"}*/ };trait B2 implements A1 { /*String whoami(){"B2"}*/ };
trait C1 implements B1 { /*String whoami(){"C1"}*/ };trait C2 implements B1 { /*String whoami(){"C2"}*/ };trait C3 implements B2 { /*String whoami(){"C3"}*/ };trait C4 implements B2 { /*String whoami(){"C4"}*/ };
trait D1 implements C1,C2,C3,C4 { /*String whoami(){"D1"}*/ };trait D2 implements C1,C2,C3,C4 { /*String whoami(){"D2"}*/ };
class E1 implements D1,D2 { /*String whoami(){"E1"}*/ };

E1 e1 = new E1()
println e1.whoami()
// 上を実行し、表示されたものからwhoami()を削除していくと、その次に到達する
// ノードが判るのでそれを繰り返すと親の順序がわかる。結果は以下のとおり
// E1 -> D2 -> C4 -> B2 -> A1 -> C3 -> C2 -> B1 -> C1 -> D1
// これは下から深さ優先探索してるのと同じ。

さてここで、@ForceOverrideが指定されたメソッドが1つでもあると、それは@ForceOverride指定されていないメソッドすべてに勝ちます(但しE1を除く。例え@ForceOverrideであろうとも、E1の普通メソッドに勝つことはない。そこまで強くはない。)。@ForceOverride同士では、やはり上と同じように、下から上への深さ優先探索(浅さ優先探索)となるのです。

(追記)
Groovy.2.3正式版では@ForceOverrideはデフォルトかつ必須の動作になり、アノテーション自体は削除されました。

この線形化順序(探索順序)の違いが実用上どういう得失として表われてくるのかというと…ものすごくわからないな。見当もつかないな…。

Groovyでしかできないこと

共通の親トレイトを必要としない形でのトレイト側メソッドの優先

(本節は、2014/4/17訂正あり。Scalaでもself type annotationにより可能とのこと。id:xuweiさん、指摘ありがとうございます。)

Scalaで、スタッカブルを構成するには、組み合せたいトレイト群から構成されるDAG全体の親となるようなトレイト(抽象もしくは具象でも別に良い)が1個必要です。それから、すべてのトレイトだけでなく、末端のクラスがそれを継承していないと、superが作れませんし、トレイトがクラスのメソッドに優先するoverrideにもなりません(いわゆる「abstract override」の話)。

このabstract overrideを使って、ScalaでAOPをやる、という記事がありますが、これだと任意のメソッドインターセプトできません。インターセプトしたいメソッドを定義したトレイトを作りかつそれをextendsしたクラスに対してしか適用できません。

(次のパラグラフは、2014/04/17追記)
しかし、Scalaには「self type annotation」という機能があり、共通の親トレイトなしでもミックスインによりクラスのメソッドをオーバーライドできます(このときsuperをスタッカブルに使えるかは未確認)。コード例はid:xuweiさんが提示してくれたこちらを参照してください。

これに対応するのですが、Groovyでも、共通の親トレイトはなしで既存クラスに対するオーバーライドができます。置換したいメソッドを持ったクラスに何らかのトレイトをextendsさせるために編集する必要も、ソースコードがある必要もありません。

例えば。

trait FileNameIsHogeHoge {
    @groovy.transform.ForceOverride
    String getPath() {
        return "hogehoge" // (1)
    }
}

class MyFile extends File implements FileNameIsHogeHoge {
    MyFile(String fileName) {
        super(fileName)
    }
}

f = new MyFile("/tmp/file.txt")
assert f.getPath() == "hogehoge"

こんな感じ。
泣き言を正直に言えば、(1)でやっぱりsuperを参照したくなることですね。わけですが、次のβを待ちましょう。

今日はこんなところで。

Scalaスケーラブルプログラミング第2版
Martin Odersky Lex Spoon Bill Venners
インプレスジャパン
売り上げランキング: 29,544

*1:自信なし!!たぶん違うと思う。

Groovy 2.3.0-betaが出たのでtraitを触ってみたメモ

Groovy 2.3.0-beta-1beta-2が出たので新機能traitをかるく触ってみました。

注意! 以下は現時点で2.3.0-beta-1と2の振舞いとドキュメントを調べた限りの情報です。正式リリースに向けて変更される可能性があります。

traitの概要と目的

Groovyのtraitは、一言で言って「実装の多重継承」を可能とする仕組みです。詳しくはこちらの本家ドキュメント(英語)をどうぞ。

GroovyおよびJava 7までのJavaでは、インターフェースは多重継承することができましたが、クラスは多重継承できませんでした。実装、すなわちメソッド本体の定義や、非public static finalなフィールド(インスタンス変数)定義はクラスでのみ可能であり、そしてクラスの継承は単一継承のみ(親が1つだけ)が可能なので、実装の継承は、ツリー型に制限されていました。北斗真拳と南斗聖拳の系列があったときDAG型の「両方の継承者」という統合はできないわけです(少なくとも実装の意味では。ジャギは実装は継承してない。上っ面のインターフェースのみです謎)。とにかく、いままでは実装の多重継承はできなかったということです。

Groovyのtraitでは以下の両方ができます。

  • クラスのようにメソッド本体やフィールドを定義する
  • インターフェースのように多重継承する

結果として、実装の多重継承ができるようになりました。

Java8のインターフェースでは、デフォルトとしての実装(メソッド本体)が定義でき、実装の多重継承もできるので、近いものがありますが、以下のような差異があります。

  • groovyのtraitではインスタンス変数(フィールド)も定義できる。つまりtraitで定義されたメソッド群の間でインスタンス固有のデータを共有・保持できる。(Scalaのtraitも同様)
  • groovyのtraitは、メソッドのデフォルト実装を定義するだけではなく、親traitのメソッドをオーバーライド定義することもできる。
  • groovyのtraitで定義したメソッドは、親クラスや他のtraitのメソッドに優先する(@ForceOverride使用。(Groovy.2.3正式版では@ForceOverrideはデフォルトかつ必須の動作になりアノテーションは削除された。) Scalaのabstract overrideの動作)
  • groovyのtraitはJava 8以前のJava VM(Java 6,7..)でgroovyを実行する場合でも利用できる(Scalaのtraitも同様)

GroovyのtraitはScalaのtraitと極めて良く似ています。traitの定義と静的な使用についてはscalaのそれとほぼ同様です*1。差異は、主に動的なtraitの実装に関するところであり、後述します。

表にまとめるとこんな感じ。

Java/Groovyクラス Java(〜Java7)およびGroovyのインターフェース Java8以降のインターフェース Groovyのtrait Scalaのtrait
定義に使用するキーワード class interface interface trait 同左
実装の単一継承 × ○(メソッドのデフォルト実装のみ) ○(フィールド使用・定義およびtrait側メソッド優先も可) 同左
実装の多重継承 × × ○(メソッドのデフォルト実装のみ) ○(フィールド使用・定義およびtrait側メソッド優先も可) 同左

コード例

たとえばこんな感じです。

trait A {}

trait B extends A {}

trait C extends A {}

class D implements B, C {}

詳しくはドキュメントをみてください。
以下もよろしければどうぞ。

www.slideshare.net

衝突!

さて実装の継承においては、多重継承だろうが単一継承であろうが、衝突というものを考慮する必要があります。

継承というのは親のフィールドやメソッドを引き継ぐということなので、子供は親のメソッドやフィールドを持っているかのように振る舞う必要があります。

単一継承であれば、お爺ちゃんと父で衝突すれば(名前が同じで実装が異なるようなものがあれば)、子供は(子供自身でオーバーライドしない限り)より近い祖先である父のものを持っているかのように振舞います。

多重継承の場合、加えて、父親(もしくはその祖先)と母親(もしくはその祖先)がそれぞれ同名で異なるメソッド実装やフィールドを持っているとき(=衝突)の考慮が必要ですが、子供はどっちのものを持っているかのように振る舞うべきでしょうか。

Groovyのtraitにおける衝突の解決もしくは回避

メソッド名に関しては、Groovyのtraitの衝突解決のデフォルトは指定したトレイトの順に、後勝ちです。つまり「implements 父,」もしくは後述の形式「withTrait(父,)」のように複数トレイトを親として指定したときに、所属トレイトを明示指定しないメソッド名が衝突していれば、指定がより後ろである側のメソッドが指定されたとみなされます。後勝ちが嫌な場合、子供でオーバーライド定義して明示的に後じゃない方を呼ぶこともできます(「トレイト名.super.メソッド名」で指定する)。ちなみにScalaでは潜在的に衝突しているとき、衝突しているメソッドを子供でオーバーライドしないとエラーになり、常に明示的な手動の衝突の解決が求められるそうですが、Groovyではそんな配慮は無いので「意図せざる偶然の衝突」に注意が必要になります。

コード例としてはこういうことです。

trait A { String foo(){"A"}}
trait B { String foo(){"B"}}

class C implements A,B {
}

class D implements B,A {
}

c = new C()
assert c.foo() == "B"
d = new D()
assert d.foo() == "A"

class E implements A,B {
    String foo(){A.super.foo()}
}

d = new E()
assert d.foo() == "A"

フィールドに関しては、フィールドの値を保持する変数名のリネームによって衝突が事前回避されます。トレイトで定義したフィールドは、そのトレイトの実装時に、

トレイトのFQCNの'.'を'_'に置換したもの+'__'+フィールド名

にリネームされた変数名のフィールドが、子クラス側で暗黙に定義されます。フィールドがprivateの場合も、publicの場合もいずれもです。子クラス側のインスタンスのフィールド名を直接指定する場合、後勝ちもクソもなく、このリネームされた名前で常に明示する必要があります。(リネームされる前、トレイト内のメソッドの定義においては、リネーム前の本来のフィールド名をソース記述上は使えます。しかしリフレクションとかでは違う名前になっているでしょうから注意)。

もっとも、フィールドがprivateであれば外部から指定することはできない(建前上不可視*2 )ので、問題になるとすればフィールドがpublicな場合のみでしょう。以下は例です。

trait A {
    public int a;
}

trait B extends A {
    public int b;
}

trait C extends A {
    public int c;
}

class D implements B, C {}

def d = new D()
println d.A__a
println d.B__b
println d.C__c

このようなフィールド名のリネームは、やや不自然に感じるかもしれませんが、フィールドを直接参照するのではなくgetter/setterを通じて扱えば、メソッド名の解決の話になりリネームされたフィールド名は隠蔽されるので、プログラマが意識することはなく、実際問題としては意識する機会はあまり多くないでしょう。

重要なのは、このリネームルールから、継承経路上に表われるすべてのトレイト実装は、そのFQCNによって単一化されるということです。ダイヤモンド継承(菱形継承)問題はこの形で解決されています。C++で言えば「仮想基底からの継承(仮想継承)」だけの扱いになるわけです。まあ、それでいいね。なんぼか直観的です。Scalaではどうなるかは知らない。

trait定義

classやinterfaceの代わりにキーワードtraitを使用します。それでだいたい期待通りに動作するでしょう。traitのコンパイル結果は、実体としては(クラスファイル上は)インターフェースといくつかの内部ヘルパクラス群になります。Javaからはトレイトはインターフェースとして見えるので、Groovyのtraitを実装しているGroovyのインスタンスJavaから扱えます。Groovyのtraitをtraitとして継承したクラスをJavaで定義するのはおそらく無理でしょう(traitを単なるインターフェースとしてJava側でimplementsすることはたぶんできる)。

静的なtrait実装

クラス定義時にトレイトを(インターフェースのように)implementsします。それでだいたい期待通りに動作するでしょう。

実行時のtrait実装

Groovyのtraitは、実行時にそれを実装したオブジェクトを作り出すことができます。Scalaも表面上似たことができるのですが、ここはGroovyとScalaで考え方が一番違うところです。

Scalaの場合を参考に

Scalaでtraitをnew時に実装するには、new..with構文を使用します。この構文は無名内部クラス構文によるインスタンスnewの拡張と言えましょう。無名内部クラスのように、内部的にクラスを生成した上でそのインスタンスを作るのです。Javaの無名内部クラスによるnewではできないこととして、複数のtraitを実装(with)する、対象クラスのサブクラスである無名内部クラスのインスタンスを生成します。これは完全に静的なものです。

たとえば、Xがクラス、Tがtraitだとしたとき

var x:X = new X() with T

であれば、xはXを継承したクラスのインスタンスであり、同時にトレイトTを継承(Scala的にはmixin)したインスタンスです。帰結として、finalクラスにはtraitを実装させることはできません。XがfinalならXのサブクラスが作れないからです。

Groovyでの実行時トレイト実装

Groovyでは、newと独立したタイミングで、既存の任意のインスタンスに対して、traitを実装した新規のプロキシインスタンス*3を作成します。

def x = new X() as T

こうです。トレイトが複数の時はこう。

def x = new X().withTrait(T1,T2)

new Xしてはいますが、そのインスタンスに対するメソッド呼び出しです。だから上はこうも書けます。

def tmp = new X()
def x = tmp as T
def tmp = new X()
def x = tmp.withTrait(T1,T2)

xはトレイトであるTやT1,T2を実装する、実行時に生成される動的プロキシのクラスのインスタンスです。Scalaとの重要な違いとして、このときのtmpとxはインスタンスが別で、かつxはXのサブクラスのインスタンスではない*4ということです。xはtmpに対するプロキシで、インスタンスのライフサイクルが違うのです。一つのtmpに対して複数回asやwithTraitで複数のプロキシを得ることもできるでしょう。

構文上の類似性があるので混同してしまうかもしれませんが、Scalaのnew時のtrait実装が無名クラス構文によるnewの拡張的な静的なものであるのに対して、Groovyの実行時trait実装はデコレータの生成であると言えます。つまり全然違います。traitの定番(?)の用途であろうDCIへの適用、つまりtraitをロールとして使用する際においては、Groovyの動作もできた方が親和性が高いとワシは思います。

Stringなどのfinalクラスにtraitを注入できる、という点も結果的な差異になります。まあ本当に注入したいのか、というのは置いておいて、ですが*5

なお、proxyから元のオブジェクトのproxyTargetというプロパティを取得できます(GROOVY-6692GROOVY-6695)。

落穂拾い

Groovyのtraitは、metaClass.mixinメソッドの上位互換*6的な代替であると見ることもできるかもしれません。metaClassは静的Groovy(@TypeChecked,@CompileStatic)配下では使えませんが、traitは使えるので、静的型チェックに親和性が高いバージョンのmixinと見ることができるかもしれません。さらに憶測ですが、traitの実行時実装の動作で奇妙にも思えるところは、metaClass.mixinのユースケースをカバーするためにそうなっているのかもしれません。

とりあえずおしまい。

*1:たぶん。Scalaは良く知らないので間違いがありましたらご指摘をお願いします。

*2:実際は思い切り見えるがw

*3:この機能は実はインターフェースに関してもともと従来のGroovyにある。「"String" as Runnable」とかやれる!!!。

*4:Groovy 2.3beta1,2では、xに対するメソッドコールはx的にmethodMissingなものについてはtmpに転送されます。そういうディスパッチを行なう「proxy」なのです。しかしXのインスタンスではなく代入互換ではない、さらにDGM非対応とか、mehodMissingなのでXよりもObjectのメソッドequals,hashCodeなどが優先されるとか、静的型チェックには対応できないとか、初学者には混乱を招き得る点がある気もする。

*5:DGMや拡張メソッド、metaClassによるメソッド注入では状態が単純には保持できないので意味あるかといえば意味はある。

*6:mixinはインスタンスを書き替えproxyを生成しないので厳密には上位互換ではない。

Java VM上の言語の覇者を決める(但しJava以外)スクリプトボウルの履歴

米国開催のJavaOneカンファレンスで恒例の人気セッションとなっている「スクリプト・ボウル」というのがあります。Java VM上で動作する言語同士のガチ勝負で、コード例とか、技術力とかいろいろな側面からパネルセッションでバトルして、その年勝者を決める、というものです。

自分では残念ながら直接見たことはないので想像ですが、「朝まで生テレビ」で言語バトルするようなものです。違うか。

エンターテイメント要素も多々あるでしょうし、人気とか「情熱」みたいなのも評価されるかもしれないし、コミュニティの「動員力」とかの要素もひょっとしたらあるかもしれませんね。そういうものも含めて、おもしろい勝負なのではないかと思います。もちろん技術者同士でやってるので、技術的なおもしろみとかもあるでしょう。プレゼンのうまさとかもあるでしょう。

んでその過去の結果をまとめてみました。

候補結果
2008Scala,Groovy,JRuby,Jython JRuby優勝 リンク
2009Scala,Groovy,JRuby,Jython,Clojure Groovy優勝 リンク
2010Scala,Groovy,JRuby,Clojure Groovy優勝 リンク
2011Scala,Groovy,JRuby ScalaとGroovyが同位優勝 リンク*1
2012Scala,Groovy,JRuby,Clojure Scalaが優勝 リンク
2013Scala,Groovy,Nashorn,Clojure Groovyが優勝 リンク

圧倒的じゃないか!!
とか言ってみる。
少なくとも皆勤賞ではある。

*1:リンク先を見るとScalaの人は相当納得してない。