2021年10月24日日曜日

3: Gradleにおける、変数およびプロパティ、そして変数スコープ

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

「local(ローカル)」に「script wide(スクリプトワイド)」スコープ?私には意味が分かりません。スクリプトクラスを視野に入れなければならず、プロパティは変数とは区別されなければなりません。

話題


About: Gradle
About: Groovy

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、「local scope variable(ローカルスコープ変数)」および「script wide scope variable(スクリプトワイドスコープ変数)」とは本当には何であるかを理解し、変数とプロパティの見分けをし、変数およびプロパティがどのように定義され使われるべきかを知る。

オリエンテーション


Groovy Gradleスクリプトの成り立ちを理解する助けになるいくつかの記事があります(ここおよびここ)。


本体

ト書き
Special-Student-7が、日本の、いくつかの山に囲まれた、古い、かなり孤立した家の、ある部屋にいる。


1: 「local scope variable(ローカルスコープ変数)」に「script wide scope variable(スクリプトワイドスコープ変数)」??


Special-Student-7-Hypothesizer
オフィシャルドキュメントのこのページは私には意味がわからない。「local scope variable(ローカルスコープ変数)」に「script wide scope variable(スクリプトワイドスコープ変数)」? . . .

「local(ローカル)」というのは'スクリプトローカル'という意味でしょう?、だって、それらの「local(ローカル)」変数がローカルであり得る先のものが私には他に何も見いだせないから。

Special-Student-7-Rebutter
ただ「local(ローカル)」と言うのは本質的に無意味であることを人々が理解してもよいころだ。'どこに対してローカル'であるかが肝心な点なのだ。

Special-Student-7-Hypothesizer
'ブロックローカル'、'ファンクションローカル'、'ソースファイルローカル'というのは意味がわかるが、ぼんやりとただローカルというのは意味がない。

とにかく、それが'スクリプトローカル'だと仮定して、「script-local(スクリプトローカル)」であることと「script wide(スクリプトワイド)」であることの間の違いを私は理解しない、少なくとも、もしも、その変数がスクリプトの先頭で定義されていたら。つまり、私が理解する限りのプログラミング言語一般の常識によれば、もしも、ある変数があるスコープの先頭で定義されていたら、その変数はそのスコープを通して可視であるはずだ。

例えば、以下のJavaまたはPythonコードにおいて、'l_string'(それはファンクションローカルである)はファンクションワイドである、インナークラスまたはインナーファンクション内においてさえも可視であって。

@Java ソースコード
	private static void test () throws Exception {
		String l_string = "a string";
		class InMethodClass {
			public void print () {
				System.out.println (String.format ("### %s", l_string));
			}
		}
		InMethodClass l_inMethodClass  = new InMethodClass ();
		l_inMethodClass.print ();
		System.exit (0);
	}

@Python ソースコード
	@staticmethod
	def test () -> None:
		l_string: str = "a string"
		def inMethodFunction () -> None:
			sys.stdout.write ("### {0:s}\n".format (l_string))
		inMethodFunction ()

「'localScope1'はスクリプトワイドではない('method ()'内で不可視である)、なぜなら、それはスクリプトローカルであるから」のような説明は、私には意味を成さない。

Special-Student-7-Rebutter
私にその意味を了解するよう期待しないでくれ。

Special-Student-7-Hypothesizer
「scriptScope」の定義に関して言えば、それはGradleではうまくいきもしないでしょう?少なくとも、私のGradleは、その定義を「Could not set unknown property 'scriptScope' for root project 'gradleTests' of type org.gradle.api.Project.」というメッセージでエラー判定する . . .

Special-Student-7-Rebutter
それはただの誤りなのか?誤りが時々起こりうることを私たちは認めなければならない。

Special-Student-7-Hypothesizer
それは、「誤り」というよりも、Gradleスクリプトは本当にはGroovyコードでない事実といかにそうであるかということを説明する手間などかけていられるものかということで、Groovyコードをただ示しているということだ。.

Special-Student-7-Rebutter
それは、Gradleにおける「スクリプトワイド変数」では全然なくて、Groovyにおける「スクリプトワイド変数」だ。なぜ、著者は、Gradleにおける「スクリプトワイド変数」を示さなかったのか、Gradle のドキュメントの中で?

Special-Student-7-Hypothesizer
そんな手間などかけていられないということなんだろう。

それに、「Groovy has two types of script variables(Groovyには2つのタイプのスクリプト変数がある)」という言明は正確でない: もう1つのタイプのスクリプト変数がある。


2: 必須である、内在するメカニズムの理解


Special-Student-7-Hypothesizer
そのドキュメントページの説明が意味をなしていないのは、それが、内在するメカニズムの必須の説明をしなかったから。

実のところ、オフィシャルドキュメントの全体が当該メカニズムの説明なしに済ませようと企図しているが、これは、そのような企図は通常うまくいかないということのもう1つの例である。

著者が親切に読者に詳細を容赦しようとしたにせよ、著者が自分自身を容赦したにせよ、それはうまくいっていない、と私は言う。

えーと、ある以前の記事にもっと詳細に説明されているとおり、Gradleスクリプトは、そのままで実行されるのではなく、'org.gradle.groovy.scripts.BasicScript'サブクラスにコンバートされ、その'run'メソッドが実行される。

そのコンバージョンにおいて、当該スクリプト内の各断片は、そのクラス内のどこへ行くのだろうか?

以下は全然正確ではないが、概念的に言って、そのドキュメントページ内のそのGradleスクリプト(それはGradleスクリプトでさえない、実のところ)は、以下のクラスのようになる(「scriptScope」が'binding.scriptScope'に変更された後で)。

@Java ソースコード
~

class Main extends org.gradle.groovy.scripts.BasicScript {
	public groovy.lang.Binding binding = new groovy.lang.Binding ();
	
	static void main (String[] args) {
		// calls the 'run ()' method somehow
		~
	}
	
	public Object run() {
		Object localScope1 = "localScope1"; // specifying 'String' does not seem to make it 'String'.
		Object localScope2 = "localScope2";
		binding.scriptScope = "binding.scriptScope";
		
		System.out.println (localScope1);
		System.out.println (localScope2);
		System.out.println (binding.scriptScope);
		
		groovy.lang.Closure closure = new groovy.lang.Closure (); // the code, "System.out.println (localScope1); System.out.println (localScope2); System.out.println (binding.scriptScope);",  is injected somehow
		
		closure.call ();
		method ();
	}
	
	private void method() {
		try {
			localScope1;
		} catch (MissingPropertyException e) {
			System.out.println ("localScope1NotAvailable");
		}
		try {
			localScope2;
		} catch(MissingPropertyException e) {
			System.out.println ("localScope2NotAvailable");
		}
		System.out.println (binding.scriptScope);
	}
}

繰り返すが、そのコードは多くの側面において正確でない、しかし、それは、ここでの議論には関係がない。

その'method ()'というファンクションはクラスメソッドになり、その他は'run ()'メソッドの中に行く。

「binding」というのは何なのか?それは、'Script'インスタンスに自動的に備え付けられる'groovy.lang.Binding'インスタンスだ。そして、'scriptScope'は、その'Binding'インスタンス(それはプロパティ群のコンテナである)のプロパティになる(変数ではなくて)。

プロパティを変数から見分けることが重要だ。

Special-Student-7-Rebutter
なぜ、それが重要なのか?

Special-Student-7-Hypothesizer
そのプロパティ群コンテナはある種のマップであり(荒く言うと)、「scriptScope」というプロパティ名はそのマップのキーになる。そのキーが利用可能であるか否かは、タイミングによる: そのキーは、同一のコードポイントから、ある時には利用可能であり、ある時には利用可能でない。したがって、プロパティの利用可能性について、スコープの観点から話しをするのは本質的に不適である。

Special-Student-7-Rebutter
それでは、そのドキュメントページによる議論は本質的に的外れなわけだ . . .


3: 「local(ローカル)」というのは、本当は'run'メソッドローカルのことだ


Special-Student-7-Hypothesizer
なるほど、これで私は理解した、「local(ローカル)」というのは、「スクリプトローカル」という意味ではなく、''run'メソッドローカル'という意味だ。

そして、これで、'localScope1'が、'method ()'内では不可視であるがそのクロージャ内では可視であるということの意味が通る。


4: 「script wide(スクリプトワイド」というのは、本当にはスクリプトワイドでない


Special-Student-7-Hypothesizer
ある先行セクションにて説明された通り、そのドキュメントページ内で「script wide variable(スクリプトワイド変数)」と呼ばれているものは、本当は変数でない。

そして、それは、本当はスクリプトワイドでもない。

例えば、以下のコードにおいて、「binding.string」は、「printData ()」のそのコールにおいて可視でない。

@Gradle ソースコード
void printData () {
	println (String.format ("### binding.string: %s", binding.string))
}

printData ()
binding.string = "string 3"

. . . そのファンクションは「binding.string」が定義される前に定義されているから当然(実のところ、「定義」という用語は適切でない、それは変数ではないのだから)?

違う、そのファンクション定義は問題ない、以下がオーケーであるという事実から分かる通り。

@Gradle ソースコード
void printData () {
	println (String.format ("### binding.string: %s", binding.string))
}

binding.string = "string 3"
printData ()

実のところ、「binding.string」はマップのキーだから、それがアクセス可能か否かは、そのキーがその時点において存在するか否かの問題であり、スコープの問題ではない。

これで、なぜ、このタイプの「変数」に変数タイプを指定できないのかということが、私に理解できる: 理由は、それは変数ではなく、マップのキーバリューペアだからだ。値たちの変数タイプ('データタイプ'と私が言わなかったことに注意)マップの定義によって既に決められていますよね?


5: 実のところ、別のタイプの変数があり、それは本当にスクリプトワイドだ: スクリプトクラスフィールド


Special-Student-7-Hypothesizer
実のところ、以下のように変数を定義することができる。

@Gradle ソースコード
@groovy.transform.Field Object i_object = "string 4"

それは、スクリプトクラスフィールドになる。

推測できる通り(もしも、内在するメカニズムが理解されていれば)、それは本当にスクリプトワイドだ。

例えば、以下は問題ない。

@Gradle ソースコード
void printData2 () {
	println (String.format ("### i_object: %s", i_object))
}

printData2 ()
println (String.format ("### i_object: %s", i_object))

@groovy.transform.Field Object i_object = "string 4"

えーと、もしも、「i_string」が定義される前に使われているかのように見えるのであれば、それは問題ない、なぜなら、その定義は、'run'メソッドのフローの中にはいないから。


6: プロジェクト追加プロパティ群もある


Special-Student-7-Hypothesizer
上述の変数やプロパティと類似の目的で使えるものとして、プロジェクト追加プロパティ群も無視されるべきでない。

プロジェクト追加プロパティというのは、プロジェクトインスタンスのプロパティであって、それが「追加」と呼ばれるのは、追加でないプロパティ群(事前設定されたプロパティ群だ)もあるからだ。

プロジェクト追加プロパティは、以下のようにセット・使用できる。

@Gradle ソースコード
void printData3 () {
	println (String.format ("### ext.string2: %s", ext.string2))
	println (String.format ("### ext.string2: %s", string2)) // "ext." can be ommited
}

ext.string2 = "string 5"
println (String.format ("### ext.string2: %s", ext.string2))
println (String.format ("### ext.string2: %s", string2)) // "ext." can be ommited
printData3 ()

なぜ、「ext」(それは、プロジェクトインスタンスのプロパティであり、スクリプトインスタンスのではない)がスクリプトクラス内でアクセス可能かという理由は、'org.gradle.groovy.scripts.BasicScript'サブクラスは、密かに介入して、そのアクセスをプロジェクトインスタンスへ送ることだ(ある以前の記事にて説明された通り)。

Special-Student-7-Rebutter
なぜ、私たちは、スクリプトプロパティの代わりにプロジェクト追加プロパティを使うべきなのか?

Special-Student-7-Hypothesizer
えーと、もしも、スクリプトインスタンスがプロジェクトインスタンスに1対1に対応しているのであれば(私が関係する限り、そうしている)、特に理由は何も私には見当たらない、実質的に言うと。

Special-Student-7-Rebutter
それでは、プロジェクト追加プロパティ群は「無視され」ていいのか、結局のところ?

Special-Student-7-Hypothesizer
しかし通常は、むしろスクリプトにバインドされたプロパティ群が無視される、なぜなら、'Project'がGradleにとってのメインエンティティだから。


7: それは、バリアントタイプのものなのか、自動推論されたタイプのものなのか?


Special-Student-7-Hypothesizer
私は本当に、変数定義において変数タイプ指定を省略することを是認しない。

例えば、私は以下のようには書かない。

@Gradle ソースコード
def l_string1 = "string 1"

それは、コードが理解しづらくなるからだ: その変数が文字列しかポイントしないと知ることは、いかなるオブジェクトをも予期しなければならないよりも、はるかに良い、もしも、その変数が、文字列しかポイントしないように意図されているのであれば。目先の少しの省力化は、長期のメンテナンス性を害するに値しない。

とにかく、その定義は何を意味しているのだろうか?それは変数をバリアントタイプにするのか、自動推論されたタイプにするのか?

それらの何が違うのか?バリアントタイプというのは、任意のタイプの任意のデータをポイントできることを意味する一方、自動推論されたタイプというのは、初期値によって定められた、ある特定のタイプであることを意味する。

実のところ、そのずさんな定義は、それをバリアントタイプにする。

そして、その変数は、ポイントされているデータのタイプにキャストされることなく使える、私はそういうコードを強く否認するが

しかしながら、悲しいことに、Gradle における変数タイプの指定はそれほど意味がないようだ、なぜなら、どの変数もバリアントになるから、以下のコードに見られる通り。

@Gradle ソースコード
String l_string2 = "string 2"
l_string2 = 2 // allowed, so the variable is variant anyway


8: 他の箇所の変数たちはどうなのか?


Special-Student-7-Hypothesizer
そのドキュメントページを最初に読んだ時に疑問に思ったのは、「えーと、他の箇所の変数たちはどうなるの?」。

例えば、以下のファンクション内の'l_string'という変数は何になるのか?

@Gradle ソースコード
void functionA () {
    String l_string
}

今、私が自然に理解するのは(変数とプロパティとを区別し、Gradleにおける変数スコープの意味を了解したからに他ならない)、そのドキュメントページの議論は、ここには当てはまらない: その変数は、明らかに、'run'メソッドローカルにもスクリプトプロパティにもならない。

任意のファンクション、任意のクラス、任意のクロージャ、等内の変数は、そのエンティティ内の、通常のJavaの意味における単なる変数である。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>