2021年3月28日日曜日

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

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

「local(ローカル)」および「script wide(スクリプトワイド)」スコープ?意味が分からない。スクリプトクラスを考えに入れなければならず、プロパティは変数とは区別されなければならない。

話題


About: Gradle

この記事の目次


開始コンテキスト


  • 読者は、プログラミング一般の基本的知識を持っている。

ターゲットコンテキスト



  • 読者は、「local scope variable(ローカルスコープ変数)」および「script wide scope variable(スクリプトワイドスコープ変数)」とは本当には何であるかを知り、変数とプロパティの見分けをし、変数およびプロパティがどのように定義され使われるべきかを知る。
  • 読者は、'データ'、'変数'、'表現'、'値'とは何であるかの正確な知識を持っている。

オリエンテーション


Gradleスクリプトはどのように正確にはGroovyコードではない(Groovyは内部的に使われてはいるが)ことを明らかにしたいくつかの記事があります(ここおよびここ)。


本体

ト書き
Hypothesizer 7は、独白する。


1: 「local scope variable(ローカルスコープ変数)」に「script wide scope variable(スクリプトワイドスコープ変数)」?意味が分からない。


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

「local(ローカル)」は'スクリプトローカル'を意味するのですよね?、だって、それらの「local(ローカル)」変数たちがローカルでありえる先のものが他に何も見えないから。

あのね、何かがローカルであると言われる場合、'どこにローカルである'かが問題なのですよ。例えば、'ブロックローカル'、'ファンクションローカル'、'ソースファイルローカル'は意味をなしていますが、漠然とただローカルだというのは無意味です。

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

例えば、以下の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 ()'内で不可視)、なぜなら、それはスクリプトローカルだから"」のような説明は、意味が分からない。

「scriptScope」の定義について言えば、それはGradleでは許されもしませんよね?少なくとも、私のGradleは、その定義をエラー判定します、以下のメッセージで: "Could not set unknown property 'scriptScope' for root project 'gradleTests' of type org.gradle.api.Project." . . .

その理由はGradleスクリプトは正確にはGroovyコードでないことであることを私は知っているが、そこには説明が必要ではありませんか?、Gradleにて「スクリプトワイド変数」を正しく定義するための説明が?

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


2: 内在するメカニズムの必要な理解


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

著者は、親切心で読者を詳細で煩わせないようにしようとしたのか、自らを煩わせなかったのか、いずれにせよ、上手くいっていない、と言わせてもらう。

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

そのコンバージョンの際、スクリプト内の各セグメントはそのクラスのどこへ行くのであろうか?

以下は正確では全然ないのだが、コンセプトとして、そのドキュメントページのその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'インスタンス(プロパティ群のコンテナである)のプロパティ(変数ではなく)になる。

プロパティは、変数とどう違うのか?えーと、そのプロパティ群コンテナは1種のマップ(荒く言って)であり、そのプロパティ名、「scriptScope」は、そのマップのキーになる。


3: 「local」は、本当には、'run'メソッドローカルのことである


Hypothesizer 7
ははあ、これで私は理解した、「local」は、「スクリプトローカル」を意味するのではなく、''run'メソッドローカル'を意味するということを。

そして、これで理屈がとおるようになった、'localScope1'が、'method ()'ファンクション内で見えないが、そのクロージャー内では見えるということの。

お分かりのように、単に「local」というのは無意味だ。


4: 「script wide」は本当にはスクリプトワイドじゃない


Hypothesizer 7
ある先行のセクションにて説明されたとおり、そのドキュメントページで「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」は、本当には変数ではなくデータ(マップの1つのキーとして)であるので、それを'スコープ'という観点から語るのは本来は適切ではない: それが当該ファンクション内で可視か否かは、スコープという概念では判断できない; それがアクセス可能か否かは、当該ファンクションがコールされるタイミングに依存する。

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


5: 実際には、もう1つ別のタイプの変数も存在し、それらは本当にスクリプトワイドである: スクリプトクラスフィールド


Hypothesizer 7
実際には、ある変数を以下のように定義することができる。

@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: プロジェクトの付加的プロパティ群もある


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

プロジェクトの付加的プロパティは、プロジェクトインスタンスのプロパティであり、それが「付加的」と呼ばれるのは、非付加的プロパティ群(あらかじめセットされたプロパティ群である)もあるからだ。

プロジェクトの付加的プロパティは、以下のようにして、セットし使用することができる。

@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'サブクラスが密かに介入して、そのアクセスをプロジェクトインスタンスへと送るからである(ある以前の記事にて説明されたとおり)。

なぜ私はスクリプトプロパティではなくプロジェクトの付加的プロパティを使うべきなのだろうか? . . . えーと、もしも、スクリプトインスタンスがプロジェクトインスタンスと1対1に対応しているのであれば(そうしている、私の関心の範囲では)、特に理由は見当たらない。


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


Hypothesizer 7
変数定義において変数タイプの指定を省略することを、私は全く是認しない。

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

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

その理由は、コードが理解し難くなるから: その変数は文字列しかポイントしないと知ることは、いかなるオブジェクトをも予期しなければならないより、はるかに良い、もしも、その変数は文字列しかポイントしないという意図のものであるならば。目先のわずかな省力化に、長期における保守性を害する値打ちはない。

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

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

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

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

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

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


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


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

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

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

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

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


参考資料


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