倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

@Category で作るカテゴリクラスはシンタックス・シュガー?

id:fumokmm 殿の記事によると、Groovy 1.8.0 (1.6 くらいからだそうですm(_ _;)m)から @Category アノテーションってのが導入されて、カテゴリクラスに定義するメソッドを static メソッドではなくインスタンスメソッドとして実装できるようになったようです。

で、カテゴリクラスにインターフェースを実装したり継承を利用したりしようとして泣く泣く諦めた経験が以前にあったので、これを期に再度挑戦してみました・・・結局うまくいかなかったけど。

参考 URL

動くサンプル


まずはどういうことをしたいかというサンプル。 カテゴリクラスとして作成したいのは、2つの double[] (double の配列)から(非負の)double 値(以下、この返り値を距離と呼びます)を返すメソッド distance() を持つクラスです。 この距離の計算として通常使われるユークリッド距離 L2(x, y) は

  

によって計算されます(wikipedia:ユークリッド距離)。 平面上では三平方の定理ピタゴラスの定理)。 これを踏まえて、以下のようにカテゴリクラス EuclideanMetricspace を定義しましょう:

import org.apache.commons.math.util.MathUtils

/** ユークリッド距離(通常の距離)による距離空間(metric space) */
@Category(double[])
class EuclideanMetricSpace {

    /** ユークリッド距離を計算 */
    double distance(double[] arg) {
        assert size() == arg.size()
        return MathUtils.distance(this, arg)
    }
}

def p1 = [1, 2] as double[]
def p2 = [4, 6] as double[]

use(EuclideanMetricSpace){
    assert p1.distance(p2) == 5d    // = √(1 - 4)**2 + (2 - 6)**2
}

ユークリッド距離の計算は MathUtils#distance() に任せてます*1。 ところで、距離の計算方法はいくつかの性質を満たせば、どんなアルゴリズムで計算してもいいので(wikipedia:距離空間, wikipedia:距離函数)、例えばユークリッド距離とは別にマンハッタン距離 L1(x, y)

  

というのも使えます(wikipedia:マンハッタン距離)。 これも上記の EuclideanMetricSpace と同じように ManhattanMetricSpace をカテゴリクラスとして使えるようにしましょう:

import org.apache.commons.math.util.MathUtils

/** マンハッタン距離による距離空間 */
@Category(double[])
class ManhattanMetricSpace {

    /** マンハッタン (Manhattan) 距離を計算 */
    double distance(double[] arg) {
        assert size() == arg.size()
        return MathUtils.distance1(this, arg)
    }
}

def p1 = [1, 2] as double[]
def p2 = [4, 6] as double[]

use(ManhattanMetricSpace){
    assert p1.distance(p2) == 7d    // = |1 - 4| + |2 - 6|
}

ここでも、距離の計算の詳細は MathUtils#distance1() メソッドに任せてます。 で、やりたいことはというと、EuclideanMetricSpace と ManhattanMetricSpace に共通のインターフェースを実装させたい!ってことです。

試み その1


まずは普通に、共通のメソッドが宣言されているインターフェース MetricSpace を定義してやりましょう:

interface MetricSpace{
    double distance(double[] arg)
}

で、2つの MetricSpace の具象クラスには、このインターフェースを実装させます(EuclideanMetricSpace のみ載せてます):

@Category(double[])
class EuclideanMetricSpace implements MetricSpace{

    @Override
    double distance(double[] arg) {
        assert size() == arg.size()
        return MathUtils.distance(this, arg)
    }
}

んで、実行してみると

Can't have an abstract method in a non-abstract class. The class 'EuclideanMetricSpace' must be declared abstract or the method 'double distance([D)' must be implemented.

ってな具合に EuclideanMetricSpace で distance() メソッドを実装せい!って怒られました。 死手留夜ん! たぶん static メソッドとして宣言されてることになってるよね・・・

その2


次は @Category アノテーションを MetricSpace の方に定義してみました。

@Category(double[])
interface MetricSpace{
    double distance(double[] arg)
}

class EuclideanMetricSpace implements MetricSpace{

    @Override
    double distance(double[] arg) {
        assert size() == arg.size()
        return MathUtils.distance(this, arg)
    }
}

すると

The method 'double distance([D, [D)' from interface 'MetricSpace' must not be static. Only fields may be static in an interface.

のように、static メソッドはダメよ!とまたも怒られた。 ドキュメントを見ると @Category は @Inherited ではないので、どっちにしろ無理でしょうけど・・・

結局


インターフェースを抽象メソッドにしても同じように怒られるし、static フィールドとか使って小細工かました実装するのも面倒なので、@Category アノテーション使ってもインターフェース指向な実装は無理ってことで。 結局のところ、@Category アノテーションは古き良きカテゴリクラスを作るための便利ツールと思っておく方がよさそうかな。
Groovyイン・アクション

Groovyイン・アクション


数学の基礎―集合・数・位相 (基礎数学)

数学の基礎―集合・数・位相 (基礎数学)

*1:MathUtils 自体をカテゴリクラスにしろ、っていうツッコミはナシで。