JavaOne2010 9/23 -- Writing Domain-Specific Languages (DSLs), Using Groovy

Groovyコミッタ、我らがPaul King*1のセッション。全面的にGroovyでお送りしております。


JavaOne最後のセッションなので、リアルタイムついったー実況、いわゆるtsudaりに挑戦してみました。


というわけで、ツイートを補足する感じでまとめてみます。

from Twitter

07:23 [DSL/Groovy]最終のコマは、Writing Domain-Specific Languages (DSLs), Using Groovy。Paulのセッション。 #javaonejp
07:25 [DSL/Groovy]Paulによると、@uehajの日本語DSLをサンプルとして混ぜ込んだらしいです。ホントかな。 #javaonejp
07:41 [DSL/Groovy]スライドの情報量が多すぎて要約できない #javaonejp

資料、SlideShareに公開されてました!
http://www.slideshare.net/paulk_asert/groovydsls

ページ数が152ページとか大杉!
真っ向から立ち向かって逐一まとめたレポートなんぞ書けません。

from Twitter

07:45 [DSL/Groovy]DSLとは?:DSLの原点、Twitterでの発言の引用、内部/外部DSLの特徴。DSLを作るときのパターンの種類名の紹介(内容はなし)、など。 #javaonejp

DSLとはなんぞや、という話が18ページぐらいまでつづきますが、情報量が多すぎてついて行けないので資料を参照してください。

from Twitter

07:45 [DSL/Groovy]次はGroovyの紹介。こんなに本が出てるんだぜ、とか。基本文法とか、Javaと比べてこんなにシンプルに書けるんだぜ、とか。 #javaonejp

というわけで、ここからずっとGroovyのターン!!!!

from Twitter

07:47 [DSL/Groovy]Grapes/Grabの紹介。アノテーションかくだけでMavenリポジトリからJarをDLしてクラスパスへの追加が自動でできますよ。 #javaonejpし

// Google Collections example 
@Grab('com.google.collections:google-collections:1.0') 
import com.google.common.collect.HashBiMap 
 
HashBiMap fruit = 
  [grape:'purple', lemon:'yellow', lime:'green'] 
 
assert fruit.lemon == 'yellow' 
assert fruit.inverse().yellow == 'lemon' 

個人的にはGroovyのキラー機能。
ポータブルなスクリプトを書くのに最高の機能です。すばらしい。

GrabResolverアノテーションを使えば、独自のMavenリポジトリを指定してあげることもできます。

from Twitter

07:48 [DSL/Groovy]Groovyを使ったDSLの例の紹介。Spockとか。 2.hours とか。 #javaonejp
07:49 [DSL/Groovy]DSLのためのGroovyの機能の紹介。import文でasを使うとクラスに任意の別名が付けられる。static importのメソッド名にも別名が付けられる。Calendar.getInstance as now とか。 #javaonejp

@Grab('com.google.collections:google-collections:1.0') 
import com.google.common.collect.HashBiMap as HashMap 
 
def m = new HashMap() 
m.key = 'value' 
assert m.inverse().value == 'key' 

とか

import static java.util.Calendar.getInstance as now 
println now().format('yyyy/MMM/dd') 

とか。

from Twitter

07:53 [DSL/Groovy]文法自体もコンパクトだからBoilerplateコードもほとんどなくて内部DSLが書きやすい #javaonejp
07:54 [DSL/Groovy]with句を使うと、メソッドのレシーバを省略しr書ける。list.with { add "a"; remove "b" } #javaonejp

map = [a:10, b:4, c:7] 
map.with { 
    assert (a + b) / c == 2 
} 
from Twitter

07:57 [DSL/Groovy]クロージャの説明。(実際Groovyのクロージャは使いやすいと思う。記述はシンプルだし、メソッドも簡単にクロージャとして取り出せるし、引数のカリー化も簡単だし、暗黙変数itもいい感じ) #javaonejp

クロージャの呼び出しは、普通のメソッド呼び出しっぽく書ける。

int myConst = 4 
def multiplier = { number -> number * myConst } 
assert multiplier(10) == 40 

実は上の呼び出し記法はシンタックシュガーで正式にはcallメソッドを呼ぶ。

Clojure other = { it + myConst } 
assert other.call(10) == 14 

↑引数宣言を省略すると、暗黙変数itとして、1つだけ引数を受け取れる。結構便利。

from Twitter

08:01 [DSL/Groovy]演算子オーバロード。Groovyではほとんどの演算子は、対応するメソッド呼び出しのシンタックスシュガーに過ぎない。メソッドを独自に実装すれば演算子の振る舞いが変更できる。 #javaonejp

たとえば、 a + b の振る舞いは、aのクラスのplusメソッドをオーバライドすると書き換えられる。

http://groovy.codehaus.org/Operator+Overloading

Operator	 Method
a + b	 a.plus(b)
a - b	 a.minus(b)
a * b	 a.multiply(b)
a ** b	 a.power(b)
a / b	 a.div(b)
a % b	 a.mod(b)
a | b	 a.or(b)
a & b	 a.and(b)
a ^ b	 a.xor(b)
a++ or ++a	 a.next()
a-- or --a	 a.previous()
a[b]	 a.getAt(b)
a[b] = c	 a.putAt(b, c)
a << b	 a.leftShift(b)
a >> b	 a.rightShift(b)
switch(a) { case(b) : }	 b.isCase(a)
~a	 a.bitwiseNegate()
-a	 a.negative()
+a	 a.positive()
Operator	 Method
a == b	 a.equals(b) or a.compareTo(b) == 0 **
a != b	 ! a.equals(b)
a <=> b	 a.compareTo(b)
a > b	 a.compareTo(b) > 0
a >= b	 a.compareTo(b) >= 0
a < b	 a.compareTo(b) < 0
a <= b	 a.compareTo(b) <= 0
from Twitter

08:03 [DSL/Groovy]Groovyのswitch文は強力。intに限らずどんなオブジェクトでもマッチングできる。もちろんString以外でもOK. #javaonejp

Java7or8で、Stringによるswitchに対応しますが、Groovyならあらゆるオブジェクトを条件式に使えます。

from Twitter

08:05 [DSL/Groovy]Builder: Groovyのビルダはツリー構造を持つ情報を定義して、それで何かを出力/実行するのにとても便利。HTML/XMLとか。SwingBuilder、AntBuilderも便利。 #javaonejp

たとえば、MarkupBuilderを使うと、

import groovy.xml.* 
def page = new MarkupBuilder() 
page.html { 
  head { title 'Hello' } 
  body { 
    ul { 
      for (count in 1..5) { 
        li "world $count" 
} } } } 

と書くだけで、↓のHTML構造が生成できます。

<html> 
  <head> 
    <title>Hello</title> 
  </head> 
  <body> 
    <ul> 
      <li>world 1</li> 
      <li>world 2</li> 
      <li>world 3</li> 
      <li>world 4</li> 
      <li>world 5</li> 
    </ul> 
  </body> 
</html> 

レポート生成スクリプトとかでも便利。

from Twitter

08:09 [DSL/Groovy]メタプログラミング:ExpandoMetaClass。クラス.metaClass.hoge = { クロージャ} で既存クラスにメソッドが追加できる。JDKのクラスでもOK. #javaonejp

クラスのmetaClassにクロージャを追加すると、クラスにメソッドを追加したことになります。

List.metaClass.sizeDoubled = {-> delegate.size() * 2 } 
LinkedList list = [] 
list << 1 
list << 2 
assert 4 == list.sizeDoubled() 

お手軽で便利です。

from Twitter

08:10 [DSL/Groovy]ReentrantLockのmetaClassにtry-finallyでロック操作するようなwithLockを追加すると、lock.withLock { クロージャ } とか書ける。 #javaonejp

import java.util.concurrent.locks.ReentrantLock 
import static System.currentTimeMillis as now 
def startTime = now() 
ReentrantLock.metaClass.withLock = { critical -> 
    lock() 
    try {   critical() } 
    finally { unlock() } 
} 
def lock = new ReentrantLock() 
def worker = { threadNum -> 
    4.times { count -> 
        lock.withLock { 
            print " " * threadNum 
            print "." * (count + 1) 
            println " ${now() - startTime}" 
        } 
        Thread.sleep 100 
    } 
} 
5.times { Thread.start worker.curry(it) } 
println "ROCK!"
from Twitter

08:12 [DSL/Groovy]Neo4Jというライブラリの自作DSLを披露。知らなかったけど組み込み型グラフデータベース、らしい。http://bit.ly/dCBwhI #javaonejp
08:14 [DSL/Groovy]ASTTransformation; Almirayのセッションでも紹介してたけど、コンパイル時に介入してコード要素の意味を変えたり、コード構造そのものを追加/変更/削除できる仕組み。コンパイル時にいじるので実行時ペナルティはない。 #javaonejp
08:15 [DSL/Groovy]まじめにASTの構造変更を書くと大変だけど、ASTBuilderを使うと直感的にとても簡単に書ける。 #javaonejp
08:18 [DSL/Groovy]Type Transformation:asを使うと型を変換できる。asType(Class)というメソッド呼び出しに相当してて、型変換は其処に実装されている。それぞれのクラスが元々対応している範囲で変換できる。 #javaonejp

def result = new AstBuilder().buildFromCode { 
    println "Hello World" 
} 

↑こういうコードは、↓というAST構造になります。

BlockStatement 
   -> ReturnStatement 
      -> MethodCallExpression 
         -> VariableExpression("this") 
         -> ConstantExpression("println") 
         -> ArgumentListExpression 
             -> ConstantExpression("Hello World") 
from Twitter

08:18 [DSL/Groovy]Gparsec。聞き逃した。 #javaonejp

HaskelのParsecのGroovy版ですね。パーサコンビネータライブラリです。
BNF的に文法を定義して、それに基づいて入力をパースする、という。たぶん。

興味があれば↓の辺をどうぞ。
http://xircles.codehaus.org/projects/gparsec

と思ったら、全然情報ないですね...。

from Twitter

08:20 [DSL/Groovy]コインのDSLサンプル(PJ Coinとは無関係)。通貨のDSLをどうやって書くか。少しずつ進化させる。 #javaonejp
08:21 [DSL/Groovy]記法の進化。使う技術によって読みやすさが変わる。"2 * quarter".value → "2 * quarter" → "2.quarter" #javaonejp
08:22 [DSL/Groovy]GEP-3(DSL用Syntax拡張の仕様):"a(1).b(2).c(3)" → "a 1 b 2 c 3" と書けるようになる。 #javaonejp

GEP-3(※仕様ID, JSRみたいなモノ)とは、Groovy1.8*2から導入予定のDSL用のシンタックス拡張です。

GEP-3の導入により、ある条件に従ってメソッド呼び出しの括弧が省略できるようになります。

その応用例として...↓

from Twitter

08:24 [DSL/Groovy]でた、@uehajの日本語DSLの例。うけてる。 #javaonejp
08:26 [DSL/Groovy]ちなみに紹介されたのは、http://bit.ly/bGYt8S平方根のやつ。 #javaonejp
09:13 [DSL/Groovy]@uehajのDSLがJavaOneで晒された瞬間 http://twitpic.com/2rbc4u #javaonejp

というわけで、JavaOneで発表された@uehaj製の日本語DSLは↓のようなもの。

// ---- 仕込み
Object.metaClass.を = Object.metaClass.の = { clos -> clos.call(delegate) }
まず = { it }
表示する = { println it }
平方根 = { Math.sqrt(it) }

// ---- 実行
まず 100 の 平方根 を 表示する // ==> 10.0

http://d.hatena.ne.jp/uehaj/20100919/1284906117

これ、ほんとにこのままGroovyコードとして実行できます。

興味のある方は1.8-beta-2をインストールしてお試しください。
なにかムズムズする感じが味わえると思います(謎

from Twitter

08:30 [DSL/Groovy]ある日顧客から唐突にPaul宛に「DSLをつくってくれ」というmailがきた、という設定のサンプル紹介。 #javaonejp
08:32 [DSL/Groovy]DSLを活用したフレームワーク/ライブラリとして、Gpars、EasyB、Spock、Cucumberなどを紹介。 #javaonejp
08:33 [DSL/Groovy]本の紹介をして、おわり。Groovy in Action 2ndとか。 #javaonejp


というわけで、Javaなアプリで一部にDSLを導入したくなったら、Polyglot Programming的にGroovyを併用して、DSLをつくることも検討してみると良いと思います。割と現実的な話として。


以上、JavaOne2010のレポートは終了です!

*1:2010年4月のQCon Tokyoで講演のため来日されていて一応顔見知りなのです。ナイスガイ

*2:現在はまだ1.8-beta-2。2010年内にはリリースされるんじゃないかと噂されてます