2021-10-10

3: Variables and Properties and Variable Scopes, in Gradle

<The previous article in this series | The table of contents of this series | The next article in this series>

"local" and "script wide" scopes? Does not make sense to me. The script class must be put into view, and properties must be discerned from variables.

Topics


About: Gradle
About: Groovy

The table of contents of this article


Starting Context



Target Context


  • The reader will understand what "local scope variable" and "script wide scope variable" really are, discern between variables and properties, and know how variables and properties should be defined and used.

Orientation


There are some articles that help you to make heads and tails of Groovy Gradle scripts (here and here).


Main Body

Stage Direction
Here is Special-Student-7 in a room in an old rather isolated house surrounded by some mountains in Japan.


1: "local scope variable" and "script wide scope variable"??


Special-Student-7-Hypothesizer
This page of the official document does not make sense to me. "local scope variable" and "script wide scope variable"? . . .

"local" means 'script-local', does it not?, because I do not see anything else to which those "local" variables could be local.

Special-Student-7-Rebutter
It is high time for people to understand that saying just "local" is fundamentally meaningless; 'local to WHERE' is the issue.

Special-Student-7-Hypothesizer
Being 'block-local', 'function-local', or 'source-file-local' makes sense, but being vaguely just local is meaningless.

Anyway, supposing that it is 'script-local', I do not understand the difference between being "script-local" and being "script wide", at least if the variable is defined at the top of the script. I mean, according to the common sense of programming in general as I understand, if a variable is defined at the top of a scope, the variable should be visible throughout the scope.

For example, in the following Java or Python code piece, 'l_string' (which is function-local) is function wide, being visible even in the inner class or the inner function.

@Java Source Code
	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 Source Code
	@staticmethod
	def test () -> None:
		l_string: str = "a string"
		def inMethodFunction () -> None:
			sys.stdout.write ("### {0:s}\n".format (l_string))
		inMethodFunction ()

An explanation like "'localScope1' is not script wide (invisible in 'method ()') because it is script-local" does not make sense to me.

Special-Student-7-Rebutter
Do not expect me to make sense of it.

Special-Student-7-Hypothesizer
As for the definition of "scriptScope", it does not even work in Gradle, right? At least, my Gradle flags that definition with an error message: "Could not set unknown property 'scriptScope' for root project 'gradleTests' of type org.gradle.api.Project." . . .

Special-Student-7-Rebutter
Is it just a mistake? We have to admit that mistakes can sometimes happen.

Special-Student-7-Hypothesizer
It is not more "a mistake" than just showing a piece of Groovy code without being able to be bothered to explain the fact that Gradle scripts are not really Groovy code and how so.

Special-Student-7-Rebutter
It is not any "script wide variable" in Gradle, but a "script wide variable" in Groovy; why did the author not show a "script wide variable" in Gradle, in a Gradle document?

Special-Student-7-Hypothesizer
He or she could not be bothered, I guess.

Besides, the statement, "Groovy has two types of script variables", is not correct: there is one more type of script variables.


2: Necessary Understanding of the Underlying Mechanism


Special-Student-7-Hypothesizer
The explanation of the document page does not make sense because it did not make the necessary explanation of the underlying mechanism.

Actually, the whole official document is scheming to do without explaining the mechanism, but this is one more example of such a scheme does not usually work.

Whether the author kindly tried to spare readers some details or spared himself or herself, it is not working, I say.

Well, as is explained in more detail in a previous article, the Gradle script is not executed as it is, but is converted to a 'org.gradle.groovy.scripts.BasicScript' sub class, whose 'run' method is executed.

At the conversion, where does each fragment in the script go in the class?

This is not accurate at all, but conceptually, that Gradle script (it is not even a Gradle script, in fact) in the document page becomes like the following class (after "scriptScope"s are changed to 'binding.scriptScope').

@Java Source Code
~

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);
	}
}

I repeat that the code is not accurate in many aspects, but that does not matter for this discussion.

The function, 'method ()', becomes a class method, and the rest go into the 'run ()' method.

What is "binding"? It is a 'groovy.lang.Binding' instance that the 'Script' instance is automatically equipped with. And 'scriptScope' becomes a property (rather than a variable) of the 'Binding' instance, which is a container of properties.

It is important to discern properties from variables.

Special-Student-7-Rebutter
Why is it important?

Special-Student-7-Hypothesizer
The properties container is a map (roughly speaking), and the property name, "scriptScope", becomes a key of the map; whether the key is available or not depends on the timing: the key is sometimes available and sometimes unavailable from the same code point. So, it is fundamentally unsuitable to talk about the availability of the property in terms of scope.

Special-Student-7-Rebutter
So, the argument by the document page is fundamentally off the mark . . .


3: "local" Is Really 'run'-Method-Local


Special-Student-7-Hypothesizer
Ah-ha, I understand now that "local" does not mean "script-local", but ''run'-method-local'.

And it now makes sense that 'localScope1' is invisible in the 'method ()' function but is visible in the closure.


4: "script wide" Is Not Really Script Wide


Special-Student-7-Hypothesizer
As has been explained in a previous section, what is being called "script wide variable" in the document page is not really any variable.

And it is not really script wide, either.

For example, in this code, "binding.string" is not visible at the call of "printData ()".

@Gradle Source Code
void printData () {
	println (String.format ("### binding.string: %s", binding.string))
}

printData ()
binding.string = "string 3"

. . . Is that a mater-of-course because the function is defined before "binding.string" is defined (in fact, the term, "define", is not appropriate, because it is not any variable)?

No, the function definition is fine, as can be seen by the fact that this is OK.

@Gradle Source Code
void printData () {
	println (String.format ("### binding.string: %s", binding.string))
}

binding.string = "string 3"
printData ()

In fact, as "binding.string" is a key of a map, whether it can be accessed or not is about whether the key exists at that moment, not about scope.

Now, I understand why this type of "variable" cannot be specified with the variable type: because it is not any variable, but a key-value pair of a map. The variable type (note that I did not say 'datum type') of values has been already determined by the definition of the map, right?


5: In Fact, There Is Another Type of Variables, Which Is Really Script Wide: Script Class Field


Special-Student-7-Hypothesizer
In fact, I can define a variable like this.

@Gradle Source Code
@groovy.transform.Field Object i_object = "string 4"

That becomes a script class field.

As can be guessed (if the underlying mechanism is understood), it is really script wide.

For example, this is fine.

@Gradle Source Code
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"

Well, if "i_string" looks as though it is being used before it is defined, it does not matter, because the definition is not inside the flow of the 'run' method.


6: There Are Also Project Extra Properties


Special-Student-7-Hypothesizer
As what can be used for similar purpose with those variables and properties, also project extra properties should not be left out.

Any project extra property is a property of the project instance, and it is called "extra" because there are also non-extra properties, which are preset properties.

A project extra property can be set and used like this.

@Gradle Source Code
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 ()

The reason why "ext" (which is a property of the project instance, not of the script instance) can be accessed in the script class is that the 'org.gradle.groovy.scripts.BasicScript' sub class secretly interferes to send the access to the project instance (as has been explained in a previous article).

Special-Student-7-Rebutter
Why should we use a project extra property instead of a script property?

Special-Student-7-Hypothesizer
Well, if the script instance corresponds to the project instance one-to-one (it does, as far as I am concerned), I do not see any particular reason, practically speaking.

Special-Student-7-Rebutter
Then, can project extra properties be "left out", after all?

Special-Student-7-Hypothesizer
But usually, rather script-binded properties are left out, because 'Project' is the main entity for Gradle.


7: Is That of a Variant Type or of an Automatically-Deduced Type?


Special-Student-7-Hypothesizer
I really do not approve omitting specifying the variable type in variable definition.

For example, I do not write like this.

@Gradle Source Code
def l_string1 = "string 1"

That is because the code becomes more difficult to understand: it is far better to know that the variable points to only a string than to have to expect whatever object, if the variable is meant to point to only a string. The immediate little labor-saving is not worth harming the long-time maintainability.

Anyway, what does that definition mean? Does that make it a variant type or an automatically deduced type?

What is the difference? Variant type means that it can point to any datum of any type, while automatically deduced type means that it is a specific type determined by the initial value.

In fact, that sloppy definition makes it a variant type.

And the variable can be used without being cast to the type of the pointed datum, although I strongly disapprove such code.

However, sadly, specifications of variable types in Gradle do not seem so meaningful, because any variable becomes variant, as can be seen in this code.

@Gradle Source Code
String l_string2 = "string 2"
l_string2 = 2 // allowed, so the variable is variant anyway


8: What About Variables in Other Locations?


Special-Student-7-Hypothesizer
What I wondered when I first read the document page was "Well, what happen to variables in other locations?".

For example, what does the variable, 'l_string', in this function become?

@Gradle Source Code
void functionA () {
    String l_string
}

As I naturally understand now (only because I have distinguished between variables and properties and have made sense of variable scopes in Gradle), the discussion of the document page does not apply here: the variable obviously does not become 'run'-method-local or become any script property.

Any variable in any function, any class, any closure, or whatever is just a variable in the entity in the usual Java sense.


References


<The previous article in this series | The table of contents of this series | The next article in this series>