uehaj's blog

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

Builderとは何か

Builderというのは、Groovyのとても特徴的な機能だと思います。たとえば、MarkupBuilder。

new MarkupBuilder().root {
   a( a1:'one' ) {
     b { mkp.yield( '3 < 5' ) }
     c( a2:'two', 'blah' )
   }
 }

こんなんを書くと、

<root>
   <a a1='one'>
     <b>3 &lt; 5</b>
     <c a2='two'>blah</c>
   </a>
 </root>

こんなのが表示されます。
でこれは何なのか? いったい何が起きているのでしょうか。
いっけん、root,a,b,cというメソッドを呼び出していますが、それらのメソッドはあらかじめ存在しているわけではありません。なのに呼び出している。実行もできている。なぜそんなことが成立するかというと、Groovyが、その「メソッド呼び出し」をメタレベルで検出し、メソッド呼び出し以外の行為をさせるからです。なにをさせるかというと、XMLのようなツリーデータ構造の構築をさせるのです。

つまりこれは詐欺みたいなものです。

何を言ってるかわからねーと思うが、メソッド呼び出しのつもりだったのに、ツリーを構築しちまったッ!!!*1

どんなツリーかというと、メソッド名を要素型(タグ名)に、引数クロージャをタグの内容に、名前引数を属性の指定と解釈するようなXMLのツリーを構築します。

a(...) { }の「{ }」のところが引数のクロージャですね。クロージャ引数として渡されるクロージャは、ツリー構築の過程で実行(call)されるのですが、このクロージャも「メソッド呼びだしのつもりがツリー構築しちまった」という詐欺的条件の元で実行されます。再帰的にcallされていくイメージ。

さて、もう1つだけトピック。

b { mkp.yield( '3 < 5' ) }

の「mkp.yield」とはなんでしょうか(とid:odashinsukeさんが疑問を呈されていました)っていうことですが、これはおまじないということで良いでしょう。XMLの地の文ていうか文字列のコンテンツとして「3<5」を埋め込む指定です。詳しくはこちら

あとは余談。普通に考えるとここは以下のように書きたいですよね。

b { '3 < 5' }

MarkupBuilderの中に文字列定数があらわれたら、それをコンテンツと扱って欲しいです。欲しいぞう。その方が自然です。でもこれはGroovyの仕様上*2できない。なぜかというと、GroovyのMOPはメソッド呼び出しを検知してそれをすりかえる(invokeMethod)という真似はできるのですが、あるいはプロパティ参照を置き換えるとかもできる(getProperty)のですが、定数リテラルの存在を検知する仕組みがないからです。この制限は、LispBuilderを作ったときにはまりました。

そういうことでした。
BuilderはとてもGroovyらしい機能です。

*1:ツリーを構築するメソッドを呼んだ、のとは違います。そんなメソッドはもともとない。

*2:AST変換を使わない限りは。