やりたいこと
Groovyで、
(擬似)連想配列やクロージャに対して、プロパティやメソッドを設定したい、
事実上そのように見える書き方をしたい
と思うことがあります。今回は後者(クロージャの拡張)の話です。
※ちなみに前者については、GroovyのMapは、mapObj['key']
という連想配列形式でアクセスできるので、Map継承クラスを作れば好きにいじれます。
今回実現したいのはこういう書式です。
sigma(0.3) // シグモイド関数の結果が返る
sigma.d(0.3) // シグモイド関数の一次導関数の結果が返る
もちろんこういう書式にせずに、単純にsigma関数とsigmad関数を別々に実装すれば済む話ですが、
設計上sigmaという一つの入れ物に関数本体をまとめたいし、かといって本関数と導関数を並列において、
sigma.original(0.3)
sigma.derivative(0.3)
みたいな仕様はどうも面倒です。sigma関数はよく使うので、シンプルにsigma()で呼びたいです。
実装例
関数定義:
class Sigma extends Closure{
Sigma(Object owner) {
super(null)
}
public double d(double val){
// 導関数が元関数の式になるのがシグモイド関数の特徴
doCall(val) * (1 - doCall(val))
}
@Override
Object doCall(Object val) {
1 / (1 + Math.exp(- val))
}
}
ユーティリティクラスがstaticに提供:
class Util{
static sigma = new Sigma()
}
利用する側:
import static Util.*
sigma(0.3)
sigma.d(0.3)
Closureクラスにはcall()もあり、正しい継承の方法がよくわかりませんが、とりあえず実現はできました。
一人前のClosure継承クラスとするのも大げさな気がしたので、できれば無名クラスとして、
class Util{
static sigma = new Closure(){
public double d(double val){
doCall(val) * (1 - doCall(val))
}
@Override
Object doCall(Object val) {
1 / (1 + Math.exp(- val))
}
}
}
としたかったのですが、無名クラスだとコンストラクタを定義できない関係でエラーになったのであきらめました。→これは単に自分がアホだっただけで、普通に無名クラスに対してコンストラクタ引数をセットすれば良い話でした(コメント欄でコード付きで指摘して頂きました)
上記はSigmaクロージャの例で、書き方の好みと、関連ソースを囲い込む(導関数が元関数を使うため)メリットしかありませんが、もっとクロージャを生かしたクラスも作れると思います。
(例)データの正規化を担当するNormalizerクロージャ
def n = new Normalizer([
[1,2,3],[4,5,6],[7,8,9],... // コンストラクタ内でカラムごとにデータを正規化して、データと正規化情報(平均と偏差)を内部で保持
])
classifier.classify(n([1,3,5]) // テストしたいサンプルデータは、正規化してから分類器にかける。n()を呪文化する。
※Normalizerを普通のクラスにした場合のイメージ
def n = new Normalizer([
[1,2,3],[4,5,6],[7,8,9],...
])
classifier.classify(n.process([1,3,5])) // ちゃんと専用メソッドを用意する
まあ要は、シグモイド関数とか導関数とか正規化関数とか、「決まり切った手順にデータを通す」のに、「専用クラスにしてメソッドを呼ぶ」のは何か大げさな気がするので、ミニクラス的にクロージャを使いたかった、ということです。