Java Scoped Assignment and Scoped Access
Jakob Jenkov |
The Java scoped assignment and scoped access are not real features in the Java programming language - but two features I think would be cool if were added to the Java language. Scoped assignment and scoped access are two language features I plan to implement for a small language I plan to implement for my Polymorph project. However, I thought I might as well suggest them for inclusion in the Java language too :-)
Scoped assignment and scoped access make it easier to express complex object graphs more concisely, and with a more readable syntax - in my opinion. They also makes it possible to use a more "fluent style" ( similar to method chaining ) to configure object graphs - without having to explicitly design the classes / APIs to support that.
I will first explain the two features separately, and then show how to use them in combination.
Scoped Assignment
Scoped assignment is a suggested language construct that changes a variable or field assignment from a statement to an expression. Here is first an example of a traditional variable assignment in Java:
String myvar = null; myvar = "Jane Doe";
The assignment is the second of these two instructions. You have expressive freedom on right side of the equals sign in the assignment expression. For instance, you can call methods on the value or pass it to method calls - for instance like this:
myvar = convertToCustomerId("Jane Doe".toUpperCase());
However, the expressive freedom only pertains to the assigned value before it is assigned. You cannot from the above construct call methods on the variable after its assignment. To do so you must add another line - like this:
myvar = convertToCustomerId("Jane Doe".toUpperCase()); myvar.verifyCustomerIdFormat();
Scoped Assignment Basic Syntax
Scoped assignment changes that. Here is an example of the same assignment using a scoped assignment - using my syntax suggestion (which may not be optimal - but it seems to work okay for readability and expressiveness):
String myvar = null; myvar:("Jane Doe");
Notice the :()
syntax construct. That is my suggested syntax for the scoped assignment.
The :(
marks the beginning of a scoped assignment, and the )
marks the end of it.
In other words, the parentheses mark the scope of the assignment.
The value inside the parentheses is the value that is being assigned to the variable. You have the same expressive freedom inside the parentheses as you have on the right side of a traditional assignment. For instance, this is valid:
myvar:( convertToCustomerId("Jane Doe".toUpperCase()) );
The scoped assignment construct returns the variable value that was just assigned - after the assignment has taken place. Thus, you can call methods on the variable after assignment. Here is an example:
myvar:( convertToCustomerId("Jane Doe".toUpperCase()) ).toLowerCase();
The method call toLowerCase()
happens to the myvar
variable after its value
has been assigned. Thus, the value returned from toLowerCase()
ends up being the value returned
from the entire expression above, but the value returned from toLowerCase()
is never used in this example.
It just disappears. But that could be changed - as we will see in the next section.
Scoped Assignment Nesting
Since a scoped assignment is an expression which returns a value, it is possible to nest a scoped assignment within another scoped assignment. Here is an example of nested scoped assignments:
String myvar1 = null; String myvar2 = null; myvar2:( myvar1:( "Jane Doe".toUpperCase() ).toLowerCase() );
Notice how this example has two nested scoped assignments. The outermost assignment is the
myvar2:(...)
assignment, and the innermost assignment is the myvar1:( ... )
assignment.
The innermost scoped assignment will result in the value "Jane Doe".toUpperCase()
being assigned
to the myvar1
variable.
The outermost scoped assignment will result in the value "JANE DOE".toLowerCase()
being assigned
to the myvar2
variable. Notice how toLowerCase()
is called on myvar1
after it is assigned the value of "Jane Doe".toUpperCase()
- so myvar1
keeps
the value "JANE DOE"
after the entire expression above has been executed.
To sum up: After the above line has executed, the values of myvar1
and myvar2
are:
myvar1 = "JANE DOE"; myvar2 = "jane doe";
Scoped Access
Scoped access is a suggested Java language construct that eases access to fields and methods within an object, and turns the entire construct into an expression. Here is first an example of a traditional access to fields and methods of a Java object:
MyObject myObject = new MyObject(); myObject.field1 = 123; myObject.field2 = "John Doe"; myObject.verifyConfiguration(); myObject.init();
Notice how each access to a field or method is prefixed with the name of the variable (myObject
)
referencing the object. This is verbose.
Scoped Access Basic Syntax
Here is how the same code would look with my suggested scoped access language construct:
MyObject myObject = new MyObject(); myObject:{ .field1 = 123; .field2 = "John Doe"; .verifyConfiguration(); .init(); }
Notice the :{}
syntax construct. That is my suggested syntax for the scoped access.
The :{
marks the beginning of a scoped access, and the }
marks the end of it.
In other words, the curly brackets mark the scope of the access.
The . in front of all the accessed fields and methods signal to the compiler that this field or method is to be found in the object the scoped access is applied to.
The scoped access language construct is itself an expression - returning the object it is applied to.
Thus, it would be possible to apply the scoped access directly to the MyObject
when it is instantiated,
like this:
MyObject myObject = new MyObject():{ .field1 = 123; .field2 = "John Doe"; .verifyConfiguration(); .init(); } ;
Using this syntax the MyObject instance can be configured already at creation time.
As you can see, the scoped access syntax makes the code look more like a JSON structure - more declarative in a sense. However, unlike in JSON you can actually call methods on an object using the scoped access syntax. You could also collapse the scoped access onto a single line, like you can with JSON:
MyObject myObject = new MyObject(); myObject:{ .field1 = 123; .field2 = "John Doe"; .verifyConfiguration(); .init(); }
Referencing the Scoped Access Target
The object the scoped access target is applied to is called the scoped access target.
As mentioned earlier, the . references the scoped access target.
In the previous examples the . is used to reference fields or methods of the scoped access target, such
as .field1
or .method1()
.
However, the . itself could be used to reference to the scoped access target itself - in case you need a reference directly to it. You will see how that can be used to express cyclic object graphs in a later section.
Scoped Access Nesting
It should be possible to nest scoped access blocks within each other. Here is an fictive example:
MyObject myObject = new MyObject():{ .field1 = 123; .field2 = new OtherObject():{ .configParam1 = "Nested"; .configParam2 = "Scoped"; .configParam3 = "Access"; }; .verifyConfiguration(); .init(); } ;
Notice the nested scoped access at field2
. Notice also, that when the nested scoped access ends,
the "scope" is returned to the outer scoped access again.
Up-referencing in Nested Scoped Access
A possible extension of the nested scoped access syntax would be to allow "up-referencing" through the scopes. Right now, a . references the current scope target (object). Subsequent . character could reference one more level up the nested scopes. For instance:
MyObject myObject = new MyObject():{ .field1 = 123; .field2 = new OtherObject():{ .configParam1 = "Nested"; .configParam2 = "Scoped"; .configParam3 = "Access"; .configParam4 = ..field1; }; .verifyConfiguration(); .init(); } ;
Notice the assignment of configParam4
inside the nested scoped access. The value assigned to
configParam4
is ..field1
. The two consecutive .. characters mean, that the
following field (or method) is found in the parent scoped access. Each consecutive . character would mean
one level more up the nested scopes, so three consecutive ... characters would refer to a scoped access two levels up
from the current scope.
Cyclic Object Graphs
The scoped access language construct makes it possible to somewhat easily express cyclic object graphs. Here is an example:
Node node = new Node():{ .addChild( new Node():{ .setParent(..); } ); };
Notice this method call inside the nested scoped access: .setParent(..);
This method call calls the setParent()
method of the inner Node object created -
passing the parent scoped access target as parameter - as referenced by the .. (double dots).
If the Node class was designed to have an extra constructor that could take a parent node as parameter (in case the created Node has a parent Node), the example above could be made even more concise:
Node node = new Node():{ .addChild( new Node(.) ); };
Notice how it is no longer necessary with the nested scoped access on the inner Node object created. Additionally, we can now just refer to the outer scoped access target directly, using a single . as reference (in the inner Node constructor parameter).
Variables Inside Scoped Access
Just like any other code scope (loops, if-statements etc.) it should be possible to declare local variables within a scoped access scope which are only visible from within that scope (or child scopes nested inside it). Here is an example:
MyObject myObject:(new MyObject():{ String fullName = "John Doe"; int indexOfSpace = fullName.indexOf(" "); .firstName:(fullName.substring(0, indexOfSpace)); .lastName :(fullName.substring(indexOfSpace+1, fullName.length())); .field2:(new OtherObject():{ .configParam1:(fullName); .configParam2:(..firstName); .configParam3:(..lastName); }); .verifyConfiguration(); .init(); });
Notice the local variable fullName
that is declared inside the outer scoped access.
This variable should only be accessible from within this scope, or other scopes nested inside it.
As you can see, the variable is accessible from within the nested scoped access too.
Combining Scoped Access and Scoped Assignment
It is possible to combine scoped access with scoped assignment. For instance, fields could be assigned inside a scoped access using scoped assignment, like this:
MyObject myObject = new MyObject():{ .field1:(123); .field2:(new OtherObject():{ .configParam1:("Nested"); .configParam2:("Scoped"); .configParam3:("Access"); .configParam4:(..field1); }); .verifyConfiguration(); .init(); } ;
Since the scoped access is itself an expression that returns the object it is applied to, you could also use scoped access inside a scoped assignment, like this:
MyObject myObject:(new MyObject():{ .field1:(123); .field2:(new OtherObject():{ .configParam1:("Nested"); .configParam2:("Scoped"); .configParam3:("Access"); .configParam4:(..field1); }); .verifyConfiguration(); .init(); });
Notice how the outer myObject
variable is now assigned using a scoped assignment.
Access-Then-Assign vs. Assign-Then-Access
The flexibility of the scoped access and scoped assignment features mean that you can choose to first access an object, then assign the result to variable, or assign the object to a variable first, then access it afterwards.
Here is first an example showing an access happening inside an assignment. Thus, the access ends up happening before the assignment.
MyObject myObject:( new MyObject():{ .field1:("Hello World"); });
Here is an example showing an assignment first, then an access of the newly assigned value (object):
MyObject myObject:(new MyObject()):{ .field1:("Hello World"); };
The difference is small, but personally I feel the second version works better than the first. In other words: Assign-then-access seems to be a little bit more useful and elegant than access-then-assign. Here is why:
When you assign first, and then access after, you can actually refer to the assigned object from within the scoped access block. You still can - using the . notation - but you could also refer to the variable directly - like this:
MyObject myObject:(new MyObject()):{ .field1:( myObject.field2.toLowerCase() ); };
However, since you would probably just use the . notation (see next example), maybe this is not a big advantage.
MyObject myObject:(new MyObject()):{ .field1:( .field2.toLowerCase() ); };
A second difference is, that with the assign-then-access construct you finish the scoped assignment scope before starting the scoped access scope. Thus, you have a little less scope nesting in that case.
Tweet | |
Jakob Jenkov |