こんいちわ。みけです。
またも、タイトルが仰々しくてすみません。
本当に大したこと書かないです。
で、相変わらずAndroidまたもや出て来ません。
gradleも出て来ません。
でも、Android Studioの登場や、gradle-android-pluginで
gradleに興味を持たれた方には読んで貰いたいと思います。
以下、本題。
Closure<V>とはなんぞや
Closureについて僕がなんか言うと、
皆が混乱するので(それだけ僕もちゃんと説明できるほど理解していない…orz)
とりあえずWikipediaのClosure でも読んで下さい。
Javaでいえば、Callable<V>みたいなものです。
で、Callable<V>に比べて便利なのが
1
2
3
4
5
6
7
Callable < String > callable = new Callable < String >() {
@Override
public String call () {
// some codes
return result . toString ();
}
};
みたいに書かなくてよいあたりです。
ちなみにCallable<T>と異なるところもあります。
それは、おいおい、説明します。
そろそろClosure<V>のサンプル見せてくれよ
というわけで、適当にサンプルをみつくろってみました。
単純な値を返すだけのClosure
1
2
3
4
5
def closure = {
'Hello, Closure!'
}
assert closure () == 'Hello, Closure!'
そうそう、groovyではreturnを省略することができます 。
その場合、最後に評価された式の値がreturnされます。
上記のClosureではClosureの最終式'Hello, Closure!'が評価され、
その値'Hello, Closure!'が返されます。
引数をとるClosure
1
2
3
4
5
6
def hello = { name ->
'Hello, ' + name
}
assert 'Hello, mike' == hello ( 'mike' )
assert 'Hello, null' == hello ()
Closure<V>とCallable<V>との決定的な違いが、
(大した違いではないが)
引数を与えることができる点です。
これをCallable<V>でやろうとすると次のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HelloTest {
public class Hello implements Callable < String > {
private final String name ;
public Hello ( String name ) {
this . name = name ;
}
@Override
public String call () throws Exception {
return "Hello, " + name ;
}
}
@Test
public void testHello () throws Exception {
assertThat ( new Hello ( "mike" ). call (), is ( "Hello, mike" ));
assertThat ( new Hello ( null ). call (), is ( "Hello, null" ));
}
}
Closureを返すClosure
1
2
3
4
5
6
7
8
9
10
11
def counter = { int offset ->
return {
offset ++
}
}
def closure = counter ( 0 )
( 0 .. 100 ). each {
assert it == closure ()
}
初期値の0からカウントするカウンターのようなクロージャーが返ってきます。
型としてはClosure<Closure<Integer>>といったところでしょうか…
で、これをみると一つ気持ち悪いところがありますね。
元の値ってどうなってしまうの?
1
2
3
4
5
6
7
8
9
10
11
12
def closure = { String hello ->
return { String name ->
hello += name
}
}
def original = 'Hello, '
def message = closure ( original )
assert message ( 'mike' ) == 'Hello, mike'
assert message ( '!' ) == 'Hello, mike!'
assert original == 'Hello, '
というように、元の値は壊れませんが、
返されるClosureが元々持っていた値は壊れていきます。
ところで、イテレーターとしてClosure<V>を使うときに出てくる
itってなんやねん?
gradleのプラグイン宣言などで、ときどきこんな記述が出てきますね
1
2
3
[ 'java' , 'groovy' ]. each {
apply plugin : it
}
で、このitですが、コレクションの一つ一つの要素です。
あ、そんなこと言われなくても知ってたって(´・ω・`)
まあ、そんなことは言わずに、すこし試してみましょう。
1
2
3
( 1 .. 4 ). each {
println "it is ${it}."
}
実行結果はこんな感じです。
1
2
3
4
it is 1 .
it is 2 .
it is 3 .
it is 4 .
これって、でもどういうふうに呼ばれているの?
groovyのDefaultGroovyMethodsクラスを介して呼ばれています。
自分でもitでアクセスできるイテレーター作ってみたい
というわけで、Closure<V>を引数にとるメソッドを書いてみました。
itで要素にアクセスできて、ちょっと嬉しいですね。
1
2
3
4
5
6
7
8
9
10
11
Integer . metaClass . define {
collect = { Closure closure ->
return new IntRange ( 0 , delegate ). collect {
closure ( it )
}
}
}
assert 2 . collect {
it * 2
} == [ 0 , 2 , 4 ]
というわけで、itは一つ一つの要素であることが事象として理解していただけたと思います。
で、itっていつ割り当てられるの?ということで、ソースをざっと見ていたんですが、
見つかりませんでした(´・ω・`)
なお調べている過程で僕も初めて知ったのですが、
Closure<V>は抽象クラスで、
コンパイル時に実際のクラスが生成されているようです。
まだまだ僕も勉強が足りませんね…
で、このへんが気になりだすと、もっと気になるのが…
Closureのスコープってどうなのよ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def outside = 'This is outside'
def doSomething = { println 'do something' }
def closure = {
def inside = 'This is inside'
doSomething ()
println outside
println inside
}
closure ()
doSomething ()
println outside
println inside
これを実効すると次のような結果が得られます。
1
2
3
4
5
6
7
8
9
10
11
12
13
do something
This is outside
This is inside
do something
This is outside
Exception thrown
5 31 , 2013 8 : 25 : 06 午後 org . codehaus . groovy . runtime . StackTraceUtils sanitize
WARNING: Sanitizing stacktrace:
groovy . lang . MissingPropertyException : No such property: inside for class: ConsoleScript83
at org . codehaus . groovy . runtime . ScriptBytecodeAdapter . unwrap ( ScriptBytecodeAdapter . java : 50 )
at org . codehaus . groovy . runtime . callsite . PogoGetPropertySite . getProperty ( PogoGetPropertySite . java : 49 )
at org . codehaus . groovy . runtime . callsite . AbstractCallSite . callGroovyObjectGetProperty ( AbstractCallSite . java : 231 )
at ConsoleScript83 . run ( ConsoleScript83: 14 )
Closure<V>のclosureの中からは、
フィールドoutsideを参照することや、doSomethingを実行することはできますが、
スクリプト本体からはclosure内部のinsideにアクセスすることはできません。
まあ、大体ご想像されていたとおりだと思います。
実はCallable<V>だったClosure<V>
さて、ここまではCallable<V>との対比でClosure<V>を見て来ましたが、
Closure<V>は実はCallable<V>を実装した抽象クラスです。
まず、Closure<V>のクラスファイルを見てみましょう。
Closure.java 1
2
3
public abstract class Closure < V > extends GroovyObjectSupport implements Cloneable , Runnable , GroovyCallable < V >, Serializable {
// some codes
}
Closure<V>はインターフェースGroovyCallable<V>を実装しています。
で、このGroovyCallable<V>ってなんやねんというと…
GroovyCallable.java 1
public interface GroovyCallable < V > extends Callable < V > { }
ってことで、Callable<V>を継承しているインターフェースになります。
まとめるとClosure<V>はCallable<V>の実装クラスということになります。
では試してみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.*
def service = Executors . newFixedThreadPool ( 3 )
def callable = { int sec ->
return {
println "${new Date().format('yyyy/MM/dd hh:mm:ss')} : Sleeping ${sec} seconds..."
Thread . sleep ( sec * 1000 )
"${new Date().format('yyyy/MM/dd hh:mm:ss')} : This is ${sec}" as String
}
}
assert service . invokeAll (( 10 .. 1 ). collect {
callable ( it ) as Callable < String >
}). each {
println it . get ()
}. size () == 10
この実行結果は次のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2013 /05/ 31 02 : 13 : 56 : Sleeping 10 seconds ...
2013 /05/ 31 02 : 13 : 56 : Sleeping 8 seconds ...
2013 /05/ 31 02 : 13 : 56 : Sleeping 9 seconds ...
2013 /05/ 31 02 : 14 : 04 : Sleeping 7 seconds ...
2013 /05/ 31 02 : 14 : 05 : Sleeping 6 seconds ...
2013 /05/ 31 02 : 14 : 06 : Sleeping 5 seconds ...
2013 /05/ 31 02 : 14 : 11 : Sleeping 4 seconds ...
2013 /05/ 31 02 : 14 : 11 : Sleeping 2 seconds ...
2013 /05/ 31 02 : 14 : 11 : Sleeping 3 seconds ...
2013 /05/ 31 02 : 14 : 13 : Sleeping 1 seconds ...
2013 /05/ 31 02 : 14 : 06 : This is 10
2013 /05/ 31 02 : 14 : 05 : This is 9
2013 /05/ 31 02 : 14 : 04 : This is 8
2013 /05/ 31 02 : 14 : 11 : This is 7
2013 /05/ 31 02 : 14 : 11 : This is 6
2013 /05/ 31 02 : 14 : 11 : This is 5
2013 /05/ 31 02 : 14 : 15 : This is 4
2013 /05/ 31 02 : 14 : 14 : This is 3
2013 /05/ 31 02 : 14 : 13 : This is 2
2013 /05/ 31 02 : 14 : 14 : This is 1
全然、問題なくCallable<V>として動いているのが確認できると思います。
で、別にここまではなんだjavaじゃん、というわけですが、
ここからがほぼ本題です。
delegate
delegateがgroovyのClosure<V>の柔軟性をもたらしていることに疑いはありません。
まずは使用例から…
groovy.xml.MarkupBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import groovy.xml.*
def w = new StringWriter ()
def doc = new MarkupBuilder ( w )
def border = 'border : solid 1px #ccc;'
def background = 'background-color : rgba(239, 239, 255, 0.7);'
def padding = 'padding : 5px;'
def radius = 'border-radius : 5px;'
doc . html ( lang : 'ja' ) {
head {
title 'test page'
}
body {
h3 'Hello Groovy!'
p ( style : "${border}${background}${padding}${radius}" ,
'This document is made by groovy.' )
}
}
println w . toString ()
これを実効すると、次のようなhtmlファイルができます。
1
2
3
4
5
6
7
8
9
<html lang= 'ja' >
<head>
<title> test page</title>
</head>
<body>
<h3> Hello Groovy!</h3>
<p style= 'border : solid 1px #ccc;background-color : rgba(239, 239, 255, 0.7);padding : 5px;border-radius : 5px;' > This document is made by groovy.</p>
</body>
</html>
MarkupBuilderというクラスはgroovy.util.BuilderSupportクラスを継承していて、
存在しないようなメソッドを実行する際に、
1
protected Object doInvokeMethod ( String methodName , Object name , Object args )
を介して実行します。
そして、このメソッドが内部でNodeを作っていきます。
そういうわけで、上記のコードの11行目のhtmlメソッドが実行されて、
"html"というノードが作成されるのはわかるかと思います。
問題はhtmlというメソッドが引数として受け入れたClosure<V>が
headというメソッドやbodyメソッドを実行しています。
さきほどのClosureのスコープに戻るとこの場合、
headやbodyというメソッドはないので、
ここでMissingMethodExceptionが発生しそうですが、
発生しません。
何故でしょうか?
これの謎を解く鍵がdelegateです。
Closure#setDelegateとClosure#setResolveStrategy
Closure<V>には変数名やメソッド名をどのオブジェクトから参照するのかを決定することができます。
そのオブジェクトへの参照がdelegateになります。
では、delegateをわざとらしいほど強調したスクリプトを見てみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setProperty 'x' , 1000
setProperty 'y' , 500
setProperty 'exec' , { println "x : ${x}, y : ${y}" }
def map = [ x : 10 , y : 5 , exec : { println "x = ${x}, y = ${y}" }]
def closure = {
println "x -> ${x}"
println "y -> ${y}"
print 'delegate exec -> '
exec . call ()
print 'owner exec -> '
exec ()
}
closure . delegate = map
closure . resolveStrategy = Closure . DELEGATE_FIRST
closure ()
では、実行してみましょう。
1
2
3
4
x -> 10
y -> 5
delegate exec -> x = 1000 , y = 500
owner exec -> x : 1000 , y : 500
面白い結果が返ってきました。
closure内部でxとyの値はmapから取得されたものになっています。
一方、execに関しては二つの結果が出てきています。
これは、15行目と16行目で行われているclosureのdelegateの設定によって説明出来ます。
closureがフィールド名を解決する仕組みは下記のようになっています。
Closure.DELEGATE_FIRSTでdelegateされたオブジェクトから順番に解決していく
delegateされたオブジェクトmapから変数を解決する
ここから、7行目、8行目のxとyはmap由来のものであることがわかります。
また10行目と12行目のexecが異なる結果となっているのは、
10行目のexecはフィールド名としてまず解決された後に、
Closure<V>が実行されています。
一方、12行目のexecではメソッドとして(map(クラスはjava.util.Map)にはメソッドexecがない)
名前解決をします。そのためclosureのOWNERであるスクリプトの方のexecが参照されます。
また、10行目と12行目のexecが参照するxとyが1行目と2行目で設定されているxとyなのは
次の理由からです。
map.execのdelegateが設定されてない
execのdelegateが設定されていない
MarkupBuilder再び
さきほどのMarkupBuilderに戻ります。
MarkupBuilder#doInvokeMethod(String, Object, Object)メソッドでは
どのような処理がされているのかというと…
MarkupBuilder.java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// this is partial codes
protected Object doInvokeMethod ( String methodName , Object name , Object args ) {
Object node = null ;
Closure closure = null ;
// creating node and attach it to variable node.
// getting closure from arg and attach it to variable closure.
if ( closure != null ) {
// stack operation
setClosureDelegate ( closure , node );
closure . call ();
// stack operation
}
// node create completion and return it
}
protected void setClosureDelegate ( Closure closure , Object node ) {
closure . setDelegate ( this );
}
9行目でsetClosureDelegateメソッドを呼び出し、
15行目からのsetClosureDelegateでは、
closureのdelegateにthis(つまりMarkupBuilderのインスタンス)を割り当てています。
その結果10行目のclosure.call()では、
メソッドおよびフィールドの名前解決がMarkupBuilderのインスタンスから行われるます。
その結果、MarkupBuilderの例でheadとかbodyといったメソッドが、
MarkupBuilderのインスタンスに対して呼び出されるということになります。
さて、翻って
gradleのtaskメソッド…
でも、Closure<V>が使われていますね。
build.gradle 1
2
3
4
5
6
7
task myTask {
description = 'this is myTask' .
println 'this is not task, but configuration.'
doLast {
println 'finished myTask'
}
}
gradleのandroidサポートが本格的になってからgradleを始めた人の中には
上記のようなgradleのProjectクラスのメソッドtask(Object, Closure)の
Closure部分をタスクだと思っている人がかなりの割合でいると思いますが、
このClosure部分はタスクではなく、設定です 。
こう書いた場合にタスクはgradleコマンドを書いた時に必ず実行されると
言っていると、元からgroovyをやっている人とは話がかみあわなくなるので、
気をつけて下さい。
では、ちょっとソースコードを覗いてみます。
AbstractProject.java 1
2
3
4
// this is partial codes
public Task task ( String task , Closure configureClosure ) {
return taskContainer . create ( task ). configure ( configureClosure );
}
AbstractProject#task(String, Closure)では、まずTaskが作成された後に、
Task#configure(Closure)が呼び出されます。
AbstractTask.java 1
2
3
4
// this is partial codes
public Task configure ( Closure closure ) {
return ConfigureUtil . configure ( closure , this , false );
}
Task#configure(Closure)ではConfigureUtil#configure(Closure, Task, boolean)が呼び出されます。
ConfigureUtil.java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// this is partial codes
public static < T > T configure ( Closure configureClosure ,
T delegate ,
boolean configureableAware ) {
return configure ( configureClosure ,
delegate ,
Closure . DELEGATE_FIRST ,
configureableAware );
}
private static < T > T configure ( Closure configureClosure ,
T delegate ,
int resolveStrategy ,
boolean configureableAware ) {
ClosureBackedAction < T > action = new ClosureBackedAction < T >(
configureClosure ,
resolveStrategy ,
configureableAware );
action . execute ( delegate );
return delegate ;
}
ここから、org.gradle.api.internal.ClosureBackedAction<T>により、
Closureが実行されます。
ClosureBackedAction.java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// this is partial codes
public ClosureBackedAction ( Closure closure ,
int resolveStrategy ,
boolean configureableAware ) {
this . closure = closure ;
this . configureableAware = configureableAware ;
this . resolveStrategy = resolveStrategy ;
}
public void execute ( T delegate ) {
// check closure is not null
// checking cinfgureableAware is false
Closure copy = ( Closure ) closure . clone ();
copy . setResolveStrategy ( resolveStrategy );
copy . setDelegate ( delegate );
if ( copy . getMaximumNumberOfParameters () == 0 ) {
copy . call ();
} else {
copy . call ( delegate );
}
}
12〜14行目でdelegateの設定、16行目or17行目でClosureの実行がなされていますね。
で、Closureのdelegateにはタスクが設定されています。
したがって、少し前で説明した通り、
Closureの実行において変数名の解決はTaskのインスタンスから順番に解決されていきます。
で、これが最終的にタスクの設定になるわけです。
(結構たらい回しにされていてイライライする方がいらっしゃるかもしれませんが、まあ、テスタビリティを上げるためにこうなっています。)
というわけで、まとめ
Closure<V>の仕組みがわかるとGradle DSL の理解が容易になります。
冒頭でgradleの話しませんって言ったな、あれは、嘘だ。