2009年7月5日日曜日

criteriaでのプロパティ指定

Grailsユーザ・ガイドCriteriaの項を読んでいて, こんな例が出ているので,



def criteria = Task.createCriteria()
def tasks = criteria.list{
eq "assignee.id", task.assignee.id
join 'assignee'
join 'project'
order 'priority', 'asc'
}

「ふーん, プロパティ名には'.'を含むナビゲーションが書けるのね」と軽く読み飛ばしていたのだが, それは一般には無理で, 'id'の場合のみ許されるようなのだ, と言うことが分かってきた.


じゃあ, それ以外の「行った先の」プロパティでクライテリアを組み立てたい場合にはどうすればいいか.



eq "assignee", Person.findByName("foo")


なんてのが一つ. でもこれはid的でないプロパティでは使いにくいだろう.



assignee { eq "name", "foo" }

でもOKぽい (つまり関連先が多でなくてもクライテリアには書ける).


単純なことだが, 備忘のために記しておく.



2009年7月1日水曜日

simple groovy code reading - MOP

MOP, Meta-object Protocolとは, プログラム自身をプログラミングの対象として扱う時のAPIです. もっとおおざっぱに言うと, Javaのリフレクションをもっと使いやすく, 柔軟に, 強力に, 綺麗にしたようなものだと思えばいいでしょう. ある意味ではいわゆるアスペクト指向プログラミングもここに含まれます (それをもっと汎用的にしたものです).


GroovyのMOPはいちばん基底では, インタフェイスgroovy.lang.MetaObjectProtocolで規定されています.



  1. MetaClass extends MetaObjectProtocol


  2. MutableMetaClass extends MetaClass


  3. MetaClassImpl implements MutableMetaClass


  4. ExpandoMetaClass extends MetaClassImpl


  5. ProxyMetaClass extends MetaClassImpl


  6. MockProxyMetaClass extends ProxyMetaClass


  7. ClosureMetaClass extends MetaClassImpl


1から3までが普通のGroovyObjectのメタクラスを表しています. 1,2 はインタフェイスで, 3はクラスです.


4から7はちょっと特殊なメタクラスです. メタクラスのレベルでいくらでもぐりぐりいじれちゃう, みたいなメタクラスも含まれています.


MOPを簡単に見てみると,



  • properties - プロパティの一覧


  • methods - メソッドの一覧


  • respondsTo - あるメソッド名+引数のメッセージを受け付けるかどうか


  • hasProperty - あるプロパティを持っているかどうか


  • invokeMethod - あるメソッド名+引数でメソッドを起動


  • getProperty/setProperty - ある名前のプロパティにアクセス


  • addInstanceMethod - メソッドの追加


おおざっぱにはこんなAPIを用意していれば, メタレベルで如何様に操れる, と言うことです. 言い換えればオブジェクトの (実行時の) 本質はたかだかこんなものだとも言えます. つまり, オブジェクトとはMapにメッセージを受け付ける機構, 受け付けたメッセージからメソッドを探索する機構, 探索したメソッドを起動する機構を付け加えたものなのです.


今日はここまで.



simple groovy code reading - closure

Groovy最大の特徴のひとつであるクロージャは, 謎でも何でもなく単なるClosureクラスのインスタンスです. なので, クロージャが何かはgroovy.lang.Closureを見てみるのがいちばんです.


Closureでいちばん目立つのは, call()とcurry()です. call()は名前のまま, そのクロージャに引数を与えて実行します. 単にmetaClassのinvokeMethodを呼んでいるだけですね.


curry()は引数の一部を固定したクロージャを作るものですが, その通り, 引数の一部を値として持つサブクラスCurriedClosureを作って返しているだけです. 実際に呼び出すときには, この固定した値をもとのClosureの引数として与えなければならないのですが, それはinvokeMethodを実装したMetaClassImplの中で行われています.


その他にも妙なものがありますよ. resolveStrategyですね. 値は



  • DELEGATE_FIRST


  • DELEGATE_ONLY


  • OWNER_FIRST


  • OWNER_ONLY


  • TO_SELF


の5種類です.


Closureはownerとdelegateの二つのプロパティを持っています. ownerはそのClosureを定義したオブジェクトです. delegateは, クロージャが自分で解決できないプロパティを参照したときに, resolveStrategyに従って探しに行くオブジェクトです. デフォルトではdelegateはownerと同じであり, resolveStrategyはOWNER_FIRSTです. したがって


class Foo { def a = 1; def aa = { println a}; def bb = { println b } }


foo = new Foo()


とすると foo().aa() は 1を出力しますが, 当然 foo().bb() はエラーになります.


ownerは変えることができませんが, delegateは変えることができますから,


class Bar { def a = 100; def b = 2 }


foo.bb.delegate = new Bar()


foo.aa.delegate = new Bar()


とすると


foo.bb()はdelegateのBarからbを探して2を出力しますが, foo.aa() は 1のままです.


ここで


foo.aa.resolveStrategy = Closure.DELEGATE_FIRST


すると, foo.aa()も先にBarからaを見つけて100を出力するようになります.


さて, このdelegateが何に役立つかというと, DSLを定義するのに便利なBuilderを作るのに役立っていたりするのです...



simple groovy code reading - operators

Groovyには, Javaに較べて多くの演算子があります. またJavaでは単なる構文要素であるものが, 演算子として再定義できたりします. それらはとても便利なのですが,



  • どんな演算子が使えるか


  • 使えたとして優先順位はどうなのか


  • 多重定義する時のメソッド名は


など, なかなかまとまって調べられる場所もないのです...


そういうときには org.codehaus.groovy.syntax.Types を見てみましょう (えーと, あまり本気にしない方がいいかも知れませんよ:-) 特によい子は真似しないように).


ここには演算子のシンボル定義や優先度が書かれています.


だからてっきり, is()に相当する "===" (昔はあった) が使えるものかと思ってやってみると...


groovy:000> 1 === 1


ERROR org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, groovysh_parse: 1: unexpected token: = @ line 1, column 5.


   1 === 1


   ^


1 error


   at java_lang_Runnable$run.call (Unknown Source)


駄目っぽいですね(^_^;)

コードの一部だけ見て, 信用しちゃいけないって, 教訓です.



simple groovy code reading - null

Groovyでの null (ナル) オブジェクトは, org.codehaus.groovy.runtime.NullObjectの (唯一の) インスタンスです.


で, NullObjectの中身を見てみると, Object invokeMethod(String name, Object args) の中で,


throw new NullPointerException("Cannot invoke method " + name + "() on null object");


しています.


これこそ我々がしょっちゅう見る, あの「文字列」の本体なのですね.


でもさ, nullにメッセージを送ったらこんな文字列が返ってくるなんて不合理だと思いませんか? オブジェクト指向以前の「関数呼び出し」の臭いが芬々と漂っているようです.


nullに何かを依頼したら, 無視して欲しい (つまり単にnullを返して欲しい) ... と自分は強く思うわけです. Objective-Cの実装はそうなっていたと思います.


# まぁそのために?.演算子があるわけですが:-)


というわけで, NullObjectのinvokeMethodを上書きしたいと思うのですが...


まずはmixinを使ってみましょうか.


class NullMixin { Object invokeMethod(String name, Object args) { return null; } }


org.codehaus.groovy.runtime.NullObject.mixin(NullMixin)


だめですねぇ. では次に, ExpandoMetaClass (EMC) を.


org.codehaus.groovy.runtime.NullObject.metaClass.invokeMethod = { String name, Object args -> null }


やっぱだめでした...



simple groovy code reading - Object methods

Javaでは, Objectに定義されているメソッドは, ごく少数で基本的なものに限られています.



  • clone


  • eqauls


  • finalize


  • getClass


  • hashCode


  • notify, notifyAll


  • toString


  • wait


それに対して, Groovyでは, お馴染みprintln始め, 非常に多くのメソッドを使うことができます.


Javaでは次のように, きわめてトップダウンかつアドホックな思考パタンになっているわけです.


「printlnの責務を持つのは当然出力を担当するやつ (PrintStream) だよな. printlnしたければPrintStreamを出力先を指定してnewして, それにprintlnすればよかろう」


「ま, しかしprintlnなんてコードのどこからでもしたいだろうし, 出力先はほとんどは標準出力だろうから, ちょっとだけ便利にしてやろう. 標準出力先としてoutというインスタンスを用意してやろう」


「しかも特定のインスタンスにどこからでもアクセスするためのJavaのやり方はstaticメンバにするのが当たり前だから, そのためにSystemというクラスも作っといてやろう」


そのお陰で我々は"System.out."というあまり意味のない枕詞をそこら中に書かされる破目になっています.

じゃ, Groovyではどう考えるか.

「あるオブジェクトの中でやりたいことがある. としたら, それはそのオブジェクトに任せるのが筋だ. どんなオブジェクトでもやりそうなことならば, それはObjectに書いておけばよい. もちろん実装では, 最終的に出力を担当するやつにやらせることになるだろうが, それはここに書くべきことじゃない. それに'System'なんて訳分からんわ」


と言うわけで, GroovyではprintlnはObjectに実装されているのです.


では, Objectには他にどんなメソッドが追加されているか, 見てみることにしましょう.


それがorg.codehaus.groovy.runtime.DefaultGroovyMethodsです. ここにはおよそ700個以上のメソッドが定義されています. ただし, これらのメソッドの第一引数の型が追加対象の型を表しますから, すべてがObjectのメソッドになるわけではありません. Javaの既存の型 (コレクションやString, Streamなど) に追加したいメソッドがここに集結しています.


中でもObjectに追加される整理して挙げておきます.



  • foo.is(bar) - Javaでいう foo == bar


  • foo.with { a = 1; b = 2 } - 自分をdelegateとしてクロージャを呼ぶ


  • getAt(), putAt() - オブジェクトをあたかもMapのように扱う仕組み. 例えば foo[a] として, プロパティaにアクセスできます


  • dump() - オブジェクト情報を書き出します. toString()を一生懸命書かなくてもいいかも知れません. "foo".dump() ===> <java.lang.String@18cc6 value=foo offset=0 count=3 hash=101574>


  • metaPropertyValues - これは直接はあまり使わない. 次のgetPeopertiesを使う


  • foo.properties - プロパティとその値の一覧. "foo".properties ===> {class=class java.lang.String, bytes=[B@2e2167}


  • use - ご存知カテゴリ


  • mixin - Foo.mixin(Bar) とするとBarのメソッドをFooに追加します. コードを見ると, Foo.mixin([Bar, Baz]) とかできるのが分かります


  • addShutdownHook {...} - 名前のとおり


  • print系


  • invokeMethod("foo", 1) - this.foo(1)


  • isCase(bar) - ご存知switch/caseで使われるものですね


  • コレクション系 - find, eachなどコレクションで使われるメソッド群


  • metaClass - メタクラスへのアクセス


  • 型変換系


なかなか面白いでしょ?


不思議なのはコレクション系ですね. 例えばListにeachがあるのは当然ですが ([1,2,3].each { println it } ), なぜObjectにまでeachがあるのでしょう?


その理由は「普通の単一のオブジェクトもひとつの要素からなるコレクション (singleton) として扱いたい」からです. だってそうなれば, 普通のオブジェクトとコレクションを区別しなくて済みますよね. Listを返すべきところで, 要素がひとつしか無ければ, JavaではそれをListで包んで返す訳ですが, Groovyならばそのまま返してもいい, ということです (そういうのを嫌がる人も多いとは思いますけどね:-)


このruntimeパッケージには他にも面白いクラスがいっぱいあります.



simple groovy code reading - import

Javaではjava.langだけはimportしなくても使えることになっています (デフォルト・パッケージ) が, それ以外はいっぱいimportしなければなりません. 特定の機能を実現するパッケージをimportで指定するのはいいとして, java.utilのような基本的な機能までいちいちimportしなければならないのは, 納得がいきませんよね.


Groovyでは



  • java.util.*


  • java.io.*


  • java.net.*


  • groovy.lang.*


  • groovy.util.*


  • BigInteger


  • BigDecimal


がデフォルトでimportされます. これはもちろんスクリプト言語として, 普通に使うものはimportせずに済むようになっているわけです.


では, simple groovy code readingの一発目として, どこでこれらがimportされているのか調べてみると...


パッケージに関しては, org.codehaus.groovy.control.ResolveVisitorにDEFAULT_IMPORTSとして定義されています. さらにこれがどこで使われているかを見ると, 同じクラスのresolveFromDefaultImports()で, 具体的なクラス・ノードがどのデフォルト・パッケージに含まれているかを調べています. BigInteger, BigDecimalについてもここで扱っています.


DEFAULT_IMPORTSはpublicなので外から見ることができます. ただしfinalなので, デフォルト・パッケージを勝手に追加することは, 残念ながらできません:-)