block) throws Ts {
try {
lock.lock();
block.call();
}
finally { lock.unlock }
}
C3S withLock
Use:
withLock lock, method { out.println "Hello" };
Given:
public interface Block0 extends Method0<Void, Ts> {}
...
public static method void withLock(Lock lock, Block0 block) throws Ts {
try {
lock.lock;
block.call
}
finally { lock.unlock }
}
Java each
Method each steps through each item in a collection. The example assumes that
collections are given new members functions (i.e. each) and given vararg constructors.
(However, modifying the collections library is really a separate issue to C3S.)
The example uses short names and is shown with minimum white space and it inlines
definitions. I don't consider this extra short formatting realistic; but it
is used in other blogs, some people prefer it, and a proposal must look OK
in many different formatting styles.
Use:
final ArrayList list=new ArrayList(1,2,3);
final Tuple1 sum=new Tuple1(0);
list.each(new Block1(){
public void call(x){sum.e1+=x;}
});
Given:
public class Tuple1{
public E1 e1;
Tuple1(E1 e1){this.e1=e1;}
}
...
public interface Block1extends Throwable>extends Method1{}
...
public extends Throwable> void each(Block1 b)throws Ts{
for(E e:es){b.call(e)}
}
C3S each
Use:
final list=ArrayList.new(1,2,3);
declare sum=0;
list.each method(x){sum+=x};
Given:
...
public method void each(Method1.interface{} b)throws Ts{
for(final e:es){b.call(e)}
}
Summary
Whilst the syntax proposed is not the shortest possible; I think it is the
best proposal to date because it balances: Clarity, Consistency, and Conciseness
and because it unifies previous proposals. The syntax synergy of the proposal
is demonstrated in the table below (syntax in table slightly simplified to
keep table short).
Table 1: Syntax Synergy in C3S
Example |
Keywords |
Comment |
|
|
|
Modifiersopt Keyword< ... >opt Typeopt Nameopt Extrasopt ( ... )opt { ... ; ... } |
|
Standard Template |
if ( ... ) { ... ; ... } |
if, for, while |
Loops and Branches |
Modifiersopt class< ... >opt Nameopt Extrasopt { ... ; ... } |
class, interface, enum |
Type Declarations |
Modifiersopt method< ... >opt Typeopt Nameopt Extrasopt ( ... )opt { ... ; ... }
|
method, new |
Method and Constructor Declarations Inside Type Declarations |
Modifiersopt final< ... >opt Typeopt Name ... , ... ;
|
final, declare |
Fields, Locals, and Arguments |
|
|
|
Type.optKeyword< ... >opt Typeopt Nameopt Extrasopt ( ... )opt { ... ; ... }
|
|
Qualified Template |
Type.optinterface { ... ; ... }
|
class, interface, enum |
Anonymous Type Declaration |
Type.optmethod< ... >opt Typeopt Nameopt Extrasopt ( ... )opt { ... ; ... } |
new, method |
Anonymous Instance as Method Arguments or Variable Assignment |
What do other people think – is this syntax an improvement on the other
proposals? Does this proposal unify the other proposals?
Appendix 1 – C3S Rules
Stage 1
The concept is to implement stage 1 rules and then see if stage 2 suggestions
are still considered necessary.
Rule 0: For a method call allow brackets, (), to be omitted
if there are no arguments and for the last call in a chain of Top Level calls
(method, constructor, or modified new, see Rules 2 and 3 below); provided that
the call isn't ambiguous with respect to a field. Top Level means
the call isn't an argument to another call. Note braces start a new statement
with a new top level. The last call means that for the top level calls only:
it is either the only call (and that call isn't qualified) or it is the call
following the last dot. The last call rule means it must follow the last dot,
even if that dot is a qualifier dot rather than a call dot; so that an integer
argument is distinct from a real argument (see last example Rule 3). This rule
is more concise. E.g.:
size = list.size;
list.set 0, 'A';
list.add( 'A' ).add 'B';
list.add string.charAt( 0 );
Rule 1: Allow final and a new keyword, declare, to infer
the type of a declaration based on the initial value. If no initial value is
given or the value is given as null then the type must be specified as normal
(also the declare keyword may be used like final is currently used). Function
arguments are as is, except that declare may prefix the declaration to emphasize
that the parameter isn't final. To specify primitives the type must explicitly
be given, see first example below. Generic arguments follow the keyword and
are useful for recursive or repeated types. This rule improves clarity by using
a keyword, consistency by always using
a keyword and allowing generic parameters, and conciseness of by allowing type
inference. E.g. (also see rules 2 and 3 for new new notation):
declare sum = 0; // Integer, for int use: int sum = 0 or declare int sum = 0
final< X > Tuple2< X, X > duplicate; // Not the same as Tuple< ?, ? >!
declare strings = ArrayList< String >.new.add "A";
final strings = ArrayList< String >.new;
Rule 2: Allow new to be used as a qualifier with a . (dot)
like a static method call (but generic arguments retain their normal position
after the type) instead of as a prefix operator. This rule improves readability
and hence clarity since it tightly associated with the type, is more consistent
with the rest of Java, and (importantly) enables Rule 3. E.g.:
declare strings = ArrayList< String >.new( "a", "b", "c" ); // Assuming ArrayList had a vararg constructor!
declare numbers = (T[])Number.new[ 1 ]; // where T extends Number
Rule 3: Allow new to infer generic type from constructor
argument and use <> to mean a raw type. This rule considerably shortens
generic declarations and also instances of anonymous inner classes. E.g.:
declare strings = ArrayList<>.new; // Explicit raw type
final strings = ArrayList< String >.new; // Explicit generic type
final strings = ArrayList.new( "a", "b", "c" ); // Inferred generic type, assuming vararg constructor
Rule 4: Allow new to declare constructors in classes and
to infer argument types from fields of the same name if no argument types are
given (like final and declare do for variables – see Rule 1). If argument
names are given without qualification and body is given as default then
infer a simple Assignment of arguments to fields. This inferring
of Assignment is similar default functionality to ML, Scala, and Fortress,
but is more flexible since multiple constructors with different access and
annotation modifiers are possible. Inside an instance method the notation this.new(
... )opt may be used. This use of new improves clarity since new
is a keyword, improves consistency since it emphasizes the similarity between
constructors and static methods and is consistent with the new use of .new
after a type name (Rule 2), and is more concise. E.g. (also see Rule 5 for
omitting trailing ; and Rule 15 for position of generic arguments):
class< E1 > T1 {
declare E1 i;
new( final E1 i ) { this.i = i }
}
class< E1 > T1 {
declare E1 i;
new( final i ) { this.i = i } // Infer argument type from field
}
class< E1 > T1 {
declare E1 i;
new( i ) { default } // Infer argument type from field and infer copy of argument into field
}
Rule 5: If an expression ends in } then there is no need
for ; before the brace (like , in array initializers and also note , at end
of argument lists is an error). IE treat ; as a statement separator rather
than a statement terminator. This rule helps the readability of instances of
anonymous classes and encourages braces to always be used even if a single
statement is possible. E.g.:
if ( end ) { break }
Rule 6: Introduce a new keyword, method, for declaring methods
which is syntactically placed just before any generic parameters (i.e. Immediately
after annotations and modifiers). If the method overrides a method then optionally
infer @Override,
other modifiers, return type, method argument types, and throws clause. If
there is only one method to override then the method name can be inferred.
If the method has no arguments the brackets may be omitted. This new keyword
is clearer because it is a keyword, is more consistent because other declarations
have keywords, and is more concise because it allows inference. E.g. (also
see Rule 9 for new generic type syntax and Rule 12 for omitting keyword return):
public static method< K, V > Map< K, V > unmodifiableMap( Map< K ?, V ? > map ) ...
method toString { "Hello" }
Rule 7: Keyword, method, can also be used with a
type followed by a dot notation to be a shorthand for creating an anonymous
instance of an anonymous inner class. This new syntax is clearer because its
intent is not obscured by verbosity, is more consistent because its syntax
is like other blocks (e.g. if), and is more concise because of inference. E.g.:
final action = AbstractAction.method ( final notUsed ) {
controller.updateAllViews updateModel
};
Rule 8: Keyword, method, can also be used to create anonymous
instances of anonymous inner classes without a type and a dot notation
if the type can be inferred from a methods arguments. This new syntax is clearer
because its intent is not obscured by verbosity, is more consistent because
its syntax is like other blocks (e.g. if), and is more concise because of inference.
E.g.:
final button = JButton.new method ( final notUsed ) {
controller.updateAllViews updateModel
};
Rule 9: Allow extends type generic arguments to be shortened
to < Type name > (i.e. like normal variable declarations), e.g.:
public static method< K, V > Map< K, V > unmodifiableMap( Map< K ?, V ? > map ) ...
public class Enum< Enum< E > E > ...
Which are equivalent to
public static < K, V > Map< K, V > unmodifiableMap( Map< ? extends K, ? extends K > map ) ...
public class Enum< E extends Enum< E > > ...
Rule 10: A throws clause can be empty, which means the method
doesn't throw anything (equivalent to an absent throws clause). This is useful
in conjunction with generics, see Rule 11 below. E.g.:
method Void call() throws;
Rule 11: Generics are extended to allow varargs (only for
use with Throwables and derivatives). An empty generics varargs list is allowed
and it is equivalent to an absent throws clause (note Rule 10 above). E.g.:
method< R, Throwable... Ts > R call() throws Ts;
Rule 12: Keyword return may be omitted for last line of
a method and the returned value is the value of the last expression, e.g.:
method toString { "Hello" };
Rule 13: If a method returns a Void, then make the end of
a method without a return and a return without an argument synonymous with
return null. E.g. the following are identical.
Given:
interface< Throwable... Ts > Block0 extends Method0< Void, Throwable... Ts > {}
Then:
final block = Block0.method {};
final block = Block0.method { return };
final block = Block0.method { return null };
final block = Block0.new {
method { return null }
};
final block = Block0.new {
@Override method call { return null }
};
final block = Block0.new {
method call { return null }
};
final Block0< RuntimeException > block = new Block0< RuntimeException >() {
Void call() { return null; }
};
Are all the same.
Rule 14: Non-final locals that are referenced by an inner
class are automatically wrapped in a final-tuple instance, like C# 3.0 does.
Note: special treatment, name mangled temporary required, of non-final arguments
is needed (not shown in example below). E.g.:
Given:
public class< E1 > Tuple1 {
public E1 e1;
public new( e1 ) { default }
}
public class< E1, E2 > Tuple2 extends Tuple1< E1 > ...
public interface< R, A1, Throwable... Ts > Method1 { R call( A1 a1 ) throws Ts }
public interface< A1, Throwable... Ts > Predicate1 extends Method1< Boolean, A1, Ts > {}
...
public static method< T, Throwable... Ts > List< T > select( final Iterable< T > c, final Predicate1< T ?, Ts > f ) throws Ts ...
Then:
final beginning = "Fred";
final names = ArrayList.new "Frederick";
final filtered = names.select method ( name ) { name.startsWith beginning };
Which is equivalent to the following verbose version:
final Tuple1< String > beginning = new Tuple1< String >( "Fred" );
ArrayList< String > names = (new ArrayList< String >( "Frederick" ));
ArrayList< String > filtered = names.select( new Predicate1< String >() {
public Boolean call( final String name ) {
return name.startsWith( beginning.e1 );
}
} );
Rule 15: Allow generic parameters to follow keyword for
class and
interface,
i.e. like method and like
Type[] for arrays. This improves consistency since in some circumstances it
cannot follow the name, e.g. method declarations. E.g.:
public class< E1 > Tuple1 {
public E1 e1;
public new( e1 ) { default }
}
Rule 16: Allow anonymous type declarations, TypeToExtend.optclass
{ ... }, for
class, interface, and enum. This is intentionally similar syntax to anonymous
instances of anonymous classes, i.e.: similar syntax to Type.optnew
( ... )opt { ...
} and Type.optmethod ( ... )opt { ... }. If used as
an initializer for a variable the type is inferred. as Class< $1 >. If used
as a method argument the type to extend may be inferred. This is a useful generalization
of the notion of creating anonymous instances of anonymous classes to the creation
of anonymous types. E.g. (also see Rule 17):
public method< Throwable... Ts > void each( final Method1< Void, E, Ts >.interface{} b )throws Ts {
for ( final e : es ) { b.call( e ) }
}
final singleton = Method0.enum { // Singleton is of type Class< $1 > where $1 implements Method0 and is an Enum
Method1 { method { ... } },
Method2 { method { ... } }
};
time singleton.values;
time enum { // Infer Method0
Method1 { method { ... } },
Method2 { method { ... } }
}.values;
Rule 17: Allow static methods to be called on variables
of type Class< T > using the normal dot notation. This is consistent with the
concept that static members are what other languages call class members, e.g.
smalltalk. E.g. (also see Rule 16):
final singleton = Method0.enum { // Singleton is of type Class< $1 > where $1 implements Method0 and is an Enum
Method1 { method { ... } },
Method2 { method { ... } }
};
time singleton.values;
Rule 18: Allow variable declarations . E.g.:
final singleton = Method0.enum { // Singleton is of type Class< $1 > where $1 implements Method0 and is an Enum
Method1 { method { ... } },
Method2 { method { ... } }
};
time singleton.values;
Stage 2
Having implemented stage 1 and gained some experience a second stage could
be added with the following suggestions (these are very much suggestions and
this section should be treated as a list of open issues). I have stuck with
syntax related suggestions that are essentially sugar and no new semantics
or modifications to JVM.
Suggestion A: Add support for statements break, continue, and exit
multiple enclosing blocks using break, continue, or return. The syntax for
break and continue for an enclosing loop would be unchanged. For exiting an
enclosing method the syntax could be MethodName.return
value;. This could be implemented using pre-made checked exceptions, thus allowing
compile time checking of multiple block exits. Syntax could be unified by allowing
Name.break and Name.continue and named blocks, e.g. if Name (
... ) { ... }.
Suggestion B: Add support for long strings with embedded variables using
a new keyword, string, e.g.:
string { // Trailing white space and single new line on { line not part of string
Embed variables including optional formatting, e.g.: %2.1f{ real }
} // Leading white space and single new line on } line not part of the string
Suggestion C: Add support for union of types using & like generics
(particularly for catch blocks).
Suggestion D: Allow final and declare to be used for the variable declaration
in a catch block and allow type inference as the
union of all declared or inferred (checked or not) exceptions.
Suggestion E: Add support for properties by inferring bodies
and declarations for: method getName { default }and method setName { default
}; i.e. like new { default }.
Suggestion F: Universally allow () in C3S
to be omitted, e.g. if condition { ... ; ... } and method Type name ...,
... { ... ; ... }.
Suggestion G: Unify variable (field, local, and
argument) declarations around the standard syntax, e.g. final Type { name1;
name2 }.
Suggestion H: Make new line act as a semicolon
and allow ,, +, -, =, etc. as last non-white space or non-comment on a line
to mean continued on next line.