dsl&builder
DESCRIPTION
Grails Code Reading 2008/05/22TRANSCRIPT
DSL&Builder
Grails Code Reading
2008/05/23
上原潤二
DSL■対象領域特化型言語
■ 楽譜♪ , MML, HTML, VHDL, *.ini, *.property, XAML, XUL, Windows のリソース定義言語 ,GIMP の Script-fu, elisp, AutoLISP, Postscript,アセンブラ ( 機械語記述専用言語 ),..
■ ⇔ 汎用言語
API と DSL 業務特化
■ 構文 汎用言語 → DSL
■ 語彙 標準 API → ドメインモデル
DSL の利点■ 文脈の限定 ( 前提の決め・制限 ),
詳細や不要機能の隠蔽による簡潔性・簡易化
■ 高密度 : 変更・レビューが容易■ 高抽象 : インピーダンスギャップ低減■ Grails では ..
■ Groovy 記述部分 (Domain, Contoller, ..) が DSL
→ Grails の主たるアドバンテージの源泉の一つ
内部 DSL■ 言語自身の自己カスタマイズ機能など
を駆使して言語上に作り上げるカスタム言語
■ ⇔ 外部 DSL( パーサをちゃんと作る )■ 作るのが面倒
■ Groovy は内部 DSL が得意
DSL の問題点■ 新しい言語を覚える必要がある■ 言語仕様のドキュメント化が必要
■ 最終的にはソースを読む
DSL にべんりな Groovy■ オペレータオーバーロード■ ルーズステートメント■ AST 変換■ クロージャ (+MOP)
■ Builder ← 今日のメインテーマ■ MOP
■ ダイナミックファインダー■ 明示的なコンパイルが不要■ fluent inteface■ 日本語メソッド名・クラス名
Builder とは何か■ AKA GroovyMarkup
■ ツリー型データ構造をクロージャ ( と MOP)を駆使して簡潔に書き下す
例えばこんな感じ (1)
ビルダー記述の一般構造< ビルダーオブジェクト> = new なんとかビルダー ()
< ビルダーオブジェクト >.< トップレベルメソッド > {
メソッド名 ( 属性名 : 属性値 , 属性名 : 属性値 ,...) {
:
}
メソッド名 ( 属性名 : 属性値 , 属性名 : 属性値 ,...) {
:
}..
}
ビルダー分類学 (1)■ ビルダー作成支援クラスから継承して作
る■ groovy.util.BuilderSupport■ groovy.util.FactoryBuilderSupport
■ 自前方式 ( 支援クラスを使用しない )
自前方式のものを「ビルダー」と呼べるかはやや ? だがここでは「広義のビルダー」としておく。
ビルダー分類学 (2)■ メソッド名を動的に作る
■ MOP で実装■ メソッド名は静的に定まっている
■ 静的にメソッドを定義しておく or MOP で実装
ビルダー実例メソッド名動的 メソッド名静的
支援クラスを使用
BuilderSupport を継承→ MarkupBuilder, DOMBuilder, AntBuilder, SAXBuilder, NodeBuilder
FactoryBuilderSupport を継承→ SwingBuilder, ObjectGraphBuilder, JmxBuilder
自前方式 ConfigSlurperCliBuilder
Grails のビルダーたち constraints, UrlMapping, BeanBuilder,WebBeanBuilder, FlowBuilder, HibernateMappingBuilder, HibernateCriteriaBuilder, JSonBuilder
ビルダーを作ろう!Let's Build a Builder!
ビルダー DSL の基本構造■ パターン 1( ビルダーを明示使用 )
< ビルダーオブジェクト >.< トップレベルメソッド > { メソッド名 ( 属性名 : 属性値 , 属性名 : 属性値 ,...) { }..}
■ パターン 2( ごくさりげない DSL)static hogehoge = { メソッド名 ( 属性名 : 属性値 , 属性名 : 属性値 ,...) { }..}
■ パターン 3( スクリプトファイルとして独立 )< トップレベルメソッド > { メソッド名 ( 属性名 : 属性値 , 属性名 : 属性値 ,...) { }..}
実例紹介■ MarkupBuilder(Groovy)
■ UrlMapping Builder(Grails)
■ ロボビルダー
MarkupBuilder■ 木は作らない■ ステートマシンで逐次出力
groovy-1.6-beta-1/src/main/groovy/xml/MarkupBuilder.java
BuilderSupportclass BuilderSupport ... { : protected abstract void setParent(Object parent, Object child); protected abstract Object createNode(Object name); protected abstract Object createNode(Object name, Object value); protected abstract Object createNode(Object name, Map attributes); protected abstract Object createNode(Object name, Map attributes,
Object value);}
groovy-1.6-beta-1/src/main/groovy/util/BuilderSupport.java
UrlMappingBuilder(1)■ こういうやつ
class UrlMappings { static mappings = { "/$controller/$action?/$id?"{ constraints { // apply constraints here } } "500"(view:'/error') }}
grails-1.0-RC1/src/grails/grails-app/conf/UrlMappings.groovy
UrlMappingBuilder(1)■ こういうやつ
class UrlMappings { static mappings = { "/$controller/$action?/$id?"{ constraints { // apply constraints here } } "500"(view:'/error') }}
■ なんだこれ?文字列? GString?
grails-1.0-RC1/src/grails/grails-app/conf/UrlMappings.groovy
UrlMappingBuilder(2)public List evaluateMappings(Closure closure) { UrlMappingBuilder builder = new UrlMappingBuilder(); closure.setDelegate(builder); // デレゲートをセットしてクロージャを呼ぶ closure.call(); :public Object invokeMethod(String methodName, Object arg) { private Object _invoke(String methodName, Object arg, Object delegate) { if(methodName.startsWith(SLASH) || isResponseCode) { // スラッシュで
始まってる場合分岐 :public Object getProperty(String name) { if(urlDefiningMode) { previousConstraints.add(new ConstrainedProperty(UrlMapping.class, nam
e, String.class)); return CAPTURING_WILD_CARD; // "(*)" 変数をワイルドカードに置き
換え } else { return super.getProperty(name); }}
grails-1.0-RC1/src/web/org/codehaus/groovy/grails/web/mapping/DefaultUrlMappingEvaluator.java
種明かし■ Groovy は文字列定数がメソッド名になる
"println"("hello"); ←OK
a="rintln"; “p$a"("hello"); ←OK
■ invokeMethod でキャプチャ可能■ GString 参照は getProperty でキャプチャ
可能
ビルダーを作ってみよう■ ロボットを定義する DSL
■ sample/robot { body(texture:"gcrlogo.png") { head(texture:"earth.jpg") arm(x:-0.25){} leg(dummy:0) }}
TransformGroup createArm(x, y, armLength, armWidth, dx=0, dy=0, dz=0, tx=null) { def trans = transformGroupBase() def shoulder = new Sphere(0.15f, Primitive.GENERATE_TEXTURE_COORDS | Primitive.GENERATE_NORMALS | Primitive.ENABLE_GEOMETRY_PICKING, createAppearance(tx)) def idou = new Transform3D(); idou.setTranslation(new Vector3d(x , y, 0)) def kaitenX = new Transform3D(); kaitenX.rotX(Math.PI*dx) def kaitenY = new Transform3D(); kaitenY.rotY(Math.PI*dy) def kaitenZ = new Transform3D(); kaitenZ.rotZ(Math.PI*dz) idou.mul(kaitenX); idou.mul(kaitenY); idou.mul(kaitenZ) trans.setTransform(idou); trans.addChild(shoulder) def trans2 = transformGroupBase(false) def arm = new Cylinder(0.1f, armLength as float, Primitive.GENERATE_TEXTURE_COORDS | Primitive.GENERATE_NORMALS | Primitive.ENABLE_GEOMETRY_PICKING, createAppearance(tx)) def idou2 = new Transform3D() idou2.setTranslation(new Vector3d(0, -armLength/2, 0)) trans2.setTransform(idou2) trans2.addChild(arm); trans.addChild(trans2) ; return trans }
以下のコードが1行に対応
TIPS■ メソッド xxx 呼び出しの括弧を省略した
い■ getXxx() というメソッドを定義 ← 手軽■ getProperty() でちゃんと定義
まとめ■ Groovy の簡潔記述能力 = Groovy 言語機能 + DSL による拡張能力■ 従って、DSL を活用する
→ Groovy の真のパワーを発揮
めんどうな 3D 処理をDSL で wrap
( ゚ Д ゚ ) ウマー
Web アプリ3階層システムをGrails が提供する DSL で wrap
( ゚ Д ゚ ) ウマー
参考リンク■ Groovy DSL roundup
■ http://www.warneronstine.com/blog/articles/2008/04/24/groovy-dsl-roundup
■ A Groovy DSL from scratch in 2 hours■ http://groovy.dzone.com/news/groovy-dsl-scra
tch-2-hours