*
Bali -- Java with a Spoonful of Syntactic Sugar
Bali -- Java with a Spoonful of Syntactic Sugar
Last updated 1998 June 16 21 Roedy Green
© 1997, 1998 Canadian Mind Products.
Bali is a proposed set of additions to Java to make the language terser
and safer. Terseness makes the language easier to read, write and maintain.
The ideas come from Abundance (See Byte Magazine October 1986), Eiffel,
Pascal, Delphi, Smalltalk, PL/I, Algol-68, Forth and even COBOL.
Not all the ideas are compatible with each other. They are intended
to stimulate discussion on language evolution, not as a formal definition
of a new language. I have ordered them so that the ones least likely to
be controversial are first.
I invite you to submit your own ideas for inclusion.
Variable Sized ( ) Display
In Java a piece of code might be displayed like this:
in a = ((b+c)/(e+f))*(g(i)+h);
That some piece of code displayed in Bali might look like this:
int a = ((b + c)/(e + f)) * (g(i) + h);
The red is just to highlight the outsized (), though colour coding matching
() and {} is not such a bad idea.. It might even be optionally displayed
like this:
b + c
int a = ----- * (g(i) + h);
e + f
Why?
-
The variable size parentheses make it easier to visually balance.
Why Not?
-
When you get deep nesting you need extra space between lines.
Assertions
Assertions should be brought over from Eiffel with as little modification
as possible. Better minds that I could concoct a more complete Java assertion
syntax. Eiffel assertions are much more extensive than I have limned here.
public int corral(int low, int value, int high)
{
require low <= high; // precondition on caller
ensure low <= result && result <= high; // guarantee of performance
if value <= low then return low;
if value >= high then return high;
return value;
} end corral
Why?
-
Assertions formally document the preconditions on a method's parameters.
Whose job is it handle invalid data, the caller or the callee?
-
Assertions formally document the postconditions on what the method does,
and what changes it makes on this.
-
Assertions constrain the behaviour of overriding methods.
-
When turned on during debugging, assertions help flush out bugs.
Why Not?
-
Assertions sometimes fail. Then what. Eiffel has a whole recovery system
quite different from try, throw, catch, finally. It would not graft well
onto Java.
Optional Named End Statements
Java tends to overuse the { } characters. It is so easy to get them unbalanced.
The compiler is no help on finding the imbalance. Further, even when they
are perfectly balanced, it is hard to match them up by eye, especially
when they are widely separated or when the source has not been through
a tidier to align them.
To solve this problem, Bali takes a leaf from PL/1 and Algol-68. I don't
dare yet propose a solution as radical as Abundance uses.
In Java you might write:
public void aMethod(int aParm) {
for (i=0; i<aParm; i++) {
System.out.println(i);
} }
It Bali you may add optional end statements: like this:
public void aMethod(int aParm) {
for (i = 0; i < aParm; i++) {
System.out.println(i);
} end for
} end aMethod
The keywords you may use after end include:
-
the name of the current method
-
the name of the current class
-
the name of the current loop
-
for, then, else, while, switch, case, class, method, init, try, catch,
finally.
If you use end statements, the compiler checks to ensure the preceding
} does indeed match that syntactic element.
Why?
-
the end statements act as documentation to make the program easier to read.
-
they are guaranteed accurate, unlike similar comments.
-
they help the compiler generate more accurate error messages for unbalanced
{ }. The compiler can nail down precisely where the mismatch occurred.
-
they are shorter to write than the corresponding comments.
-
they make it more likely that when you insert new code into an existing
program you will put it in the correct place, particularly adding a new
method to the class just before the final } of the class.
Why Not?
-
end xxxx is quite verbose just to mark the ends of blocks. It might
be wiser to use more compact icons to mark begin and end.
Explicit modifiers
Permit the use of the modifier instance to explicitly declare a
variable or method as non-static. Permit the use of the modifier
package or friend
to explicitly declare a variable or method as not private, protected or
public.
Why?
-
In Java, you can declare a method or variable static, but there is no way
to explicitly declare it not static. The lack of a declaration could be
an oversight, or it could be deliberate. You can't tell. Similarly for
friend.
-
There is currently not even a vocabulary to talk about friend visibility.
-
There is no target string to search in the source code for friend and instance
methods.
-
It makes people think about whether a method should be/is static or instance.
So often programmers are puzzled when they use instance methods as if they
were static. The instance keyword would jog their memories.
Why Not?
-
There are already scads of modifiers. New modifiers would just clutter
code. Explicit modifiers would only make sense if you selected the modifiers
with radio buttons in the source code editor.
Extended Case
In Bali case labels can have the following forms:
-
integer constants
-
ranges, e.g. 300..500
-
strings
-
variables (including objects compared with .equals)
-
full boolean expressions, e.g. isValid(x)
-
partial boolean expressions e.g. > 10
-
partial instanceof expressions, e.g. instanceof Dog
These are compiled to a combination of nested if, jump tables and lookup
tables.
Why?
-
It is easier to proofread code to see that all cases have been handled.
-
It is easier to read code to associate the actions with the conditions.
-
It is terser than the equivalent ifs or individual case labels.
-
The default case can be used to ensure no cases were overlooked.
-
They could be used for regular expressions.
Why Not?
-
It is fairly easy for a case to accidentally fall between the cracks and
be handled by default.
-
Compilers don't know enough to safely generate optimal code for the cases.
Humans writing code know more and can therefore generate faster code even
if the code is harder to follow.
-
It is cool to have ifs nested so deep that it doesn't fit on your screen
any more, even though you ident only one character per if-statment.
Delphi-like JavaBean Properties
Bali properties are almost identical to those in Delphi.
In Java you write:
class MyClass {
private int height ;
public int getHeight() { return height; }
public void setHeight(int height)
{ if ( 50 < height && height < 275 )
this.height = height;
}
}
...
x.setHeight(x.getHeight()+1);
In Bali you would write that as:
class MyClass {
private int _height ;
public void setHeight(int height)
{ if ( 50 < height && height < 275 )
this._height = height;
}
published property height get _height set setHeight;
}
...
x.height++;
published is a sort of super public declaration that means
this property should be visible in the beanbox.
If you leave out the get or set clause, you suppress the ability to
read or write the property.
Why?
-
Efficiency: Since the getHeight routine is a dummy that does nothing but
waste ram and cpu cycles, you can discard it and declare the property to
allow direct read access to the associated internal variable.
-
Properties let you convert a public variable into a pair of guard functions
without having to change the client code.
-
Client get/sets written with properties are much easier to read and write.
They look just like ordinary variables.
Why Not?
-
Property already has a meaning in Java -- a list of keyword/value
pairs. Some other name such as attribute should be used for such
a feature.
-
Clients should be aware whether they are using a function or a variable.
-
Compenent programmers never use public variables. They set them up as functions
right off the bat even if the routines are just dummies. Thus there is
no need ever to flip client code when a public variable changes to a function.
-
Perhaps it might be better to simply allow functions without arguments
to be written without trailing (). That would allow you to convert a variable
to an accessor function without changing any client code without coding
any extra declarations.
-
Properties are an attempt to invent PL/I-style pseudo functions, functions
that can be used on the left hand side of the = sign. Another way of looking
at the problem, properties are an attempt to allow you to override the
effect of the = operator. Perhaps instead of properties, we should invent
a more general mechanism that allows overloaded functions that can be used
on either side of the = sign, with multiple arguments. Then we would have
a way of dealing with tuples returned from a function. If a function can
have multiple inputs, why should it not have multiple outputs? If a function
can accept several variables to access, surely a function should be able
to accept several variables to store. A more general mechanism like this
would lead to simpler syntax for accessing the Vector methods.
Enumerations
Java currently has no enumerations. There are only static final
constants. There is no formal connection between groups of constants, or between
the groups of constants and the
the variables that use them. There is no formal mechanism to distinguish the two
kinds of enumeration constants:
- enumeration integer constants that represent one of a possible list of choices.
- mask constants that can be ORed together to represent
combinations of choices.
I suggest Java should properly support enumerations. It would work
like C's enum, e.g.
enum DaysOfWeek { SUN=0, MON=1, TUE, WED, THU, FRI, SAT }
You could then declare a variable as:
DaysOfWeek weekday = DaysOfWeek.SAT;
Enum's could be nested inside a class, and made public. Inside the
class you would use WED, outside DaysOfWeek.WED.
Enums could be free-standing in which case they would be like
interfaces. You could implement them in a class and then be able to
use the unqualified names inside that class.
To handle enums that are individual bits I suggest a syntax like this:
set PizzaToppings { MOZZARELLA=0x1, PEPPERONI=0x2, TOMATOES=0x4,
GREENPEPPER, PINEAPPLE, GOUDA, CRANBERRIES }
You could also create enums and sets that exended other enums or
sets. You would have a cast of the form (DayOfWeek) or
(PizzaToppings) that converted a plain int into a member of
that enum. It would raise an exception if it did not match one of
the official values of the enum or set.
Why?
- Various kludges using enumeration objects won't work in case
clauses, don't have a simple predicatable external representation,
and have the extra overhead of an object rather than a simple int.
- It ensures you don't use a constant from the wrong enumeration group.
- It ensures you use an enumeration constant in the correct way, as a value
or as a bit.
- It makes maintaining code much easier. It is quite difficult without
enumerations to figure out which constants are legitimate to use with
which variables. My suggested scheme makes it completely clear, and
recruit's the compiler's help to ensure the rules are followed.
- The formal connection enables a debugger to display the enum value
by name, rather than just by value.
Why Not?
- It may be too complicated to figure out the type rules. What
sorts of arithmetic are you allowed on the various enumeration values
without casts to store them back in variables? You want to avoid casts
that could introduce errors. You probably want + int, - int for regular
enumerations, | & ~ for set style enumerations. How much run type
checking should be done to ensure enumeration constants are always valid.
Augments
The augments keyword is much like the extends keyword.
- It stops you from deliberately or accidentally overriding any
method in the base class.
- It lets you extend even final classes with additional convenience
methods without creating wrapper methods.
Why?
- Saves writing repetitive wrapper methods.
- Ensures methods are not accidentally overridden, especially
in superclasses of the immediate base class.
- Ensures methods are not accidentally overridden if the base class is later
updated.
Why Not?
- Perhaps this should better be handled with explicit
override, original, overridable
keywords.
Guard Routines
Bali guard routines have the same function as Delphi properties, but they
have a terser definition syntax, and less ability to explicitly invoke
or bypass the guard routines.
In Java you write:
class MyClass {
private int height ;
public int getHeight() { return height; }
public void setHeight(int height)
{ if ( 50 < height && height < 275 )
this.height = height;
}
}
...
x.setHeight(x.getHeight()+1);
In Bali you would write that as:
class MyClass {
private int height
get { return height; }
set { if ( 50 < value && value < 275 ) height = value; }
}
...
x.height++;
get, set and value are reserved words. get
and set introduce guard routines for the previously defined variable.
If you leave out the get or set clause, you suppress the
ability to read or write the variable from outside the class. Inside the
class, or in the code of its descendents, you access directly, bypassing
the guard routines. get and set define the public access.
Why?
-
The same reasons as for Delphi-like properties apply.
-
You need only one identifier. With other approaches you need three.
Why Not?
-
The same reasons against properties apply.
-
This means every property has to be associated with an internal variable.
The scheme does not lend itself to virtual purely computed field that has
no associated internal variable.
-
It gets confusing. With the same syntax, sometimes you bypass the guard
routines, sometimes not. Clients should be aware whether they are using
a function or a variable. There should be a way to control explicitly when
the guard routines are bypassed. However, there is nothing to stop you
from writing a named guard routine and invoking it in the get or set, and
explicitly invoking it where needed inside the class or its descendants.
Perhaps you could invent a syntax like height.set = height.get + 1;
to explicitly invoke the guard routines.
Constructor Shortcut
In Java you would write:
BigDate d = new BigDate(1997,05,06);
In Eiffel you would write:
d: BigDate;
...
d.Create(1997,05,06);
In Bali you can use the usual Java syntax or this shortcut:
BigDate(1997,05,06) d;
Why?
-
You don't have to write the name of the class twice.
-
You are less likely to forget to initialise.
-
It is impossible to create an object of the wrong type.
Why Not?
-
The new syntax masks the fact you are creating a new object.
-
The new syntax looks too much like an ordinary method call.
Enumerate Shortcut
In Java you might write:
for ( CitiesByState enum=new CitiesByState();
enum.hasMoreElements(); ;)
{
City city=enum.nextElement();
println(city.population);
}
In Bali, you could abbreviate this to:
for each city in CitiesByState
{
println (city.population);
}
Why?
-
it is a lot easier to read and proofread.
-
there is less likelihood of error typing it.
-
it opens the door for abbreviations to other for idioms.
Why Not?
-
The Java scheme gives you extra typing practice and makes your keystroke
per day output higher.
Null Method Shortcut
In Java you might write:
if (person != null) person.requestPassport();
In Bali, you could abbreviate this to:
person.?requestPassport();
Why?
-
There is no possibility of a minor mismatch of the name tested in the if
and the variable used to access the method.
-
Programmers are lazy and tend to ignore null cases. If you make it easy
for them to deal with them, they are more likely to keep them in mind.
Why Not?
-
The ? already has meaning. This new syntax does violence to that existing
meaning. The dual use would make code harder to read.
Conversions
In Java, casting is used for two quite different purposes:
-
to request a conversion
-
to request that an object be treated as an instance of some sub or superclass
without actual conversion.
There are literally hundreds of different ways of requesting a type 1 cast.
The simple (type) notation only works on a handful of cases. Type 2 casts
are uniformly done with (ClassName).
In Bali there are similarly two types of cast, but the way you request
them is totally uniform:
-
(DesiredType) -- for conversion.
-
(as DesiredType) -- for treatment as sub/superclass.
You can even convert objects to primitives, objects to objects, and primitives
to objects with a type 1 cast.
How are the conversions to objects done? by looking for a constructor
that takes a primitive or object as the sole argument.
How are conversions from objects to primitives done? by looking for
methods with the names intValue(), longValue(), etc.
How are conversions from primitives to primitives done? conceptually
by looking for static methods with names like Convert.intValue(double);
though in practice these are handled inline as special cases.
Why?
-
Simplication. Code is easier to write and read when there is a uniform
way of requesting conversions.
-
accuracy. You are more likely to get the correct method.
-
it makes it clear when data are actually being transformed.
Why Not?
-
It requires changing existing code to insert the as.
Generic Cast (convert)
I further suggest that the generic caster (convert) be allowed
to convert where the type of the target is known. E.g.
float x = 1.0f;
// the following three lines are equalent ways of converting from
// float to String
String s = (String) x;
String t = (convert) x;
String u = String.valueOf(x);
Why
-
If the types of x or t change, e.g. x becomes a double, all the code automatically
adjusts to suit. You only need change the declarations. The cardinal rule
of writing maintainable code is that you specify each fact in one and only
one place. Java flagrantly violates that rule in three places: casts, conversion
functions, and primitive temporary variables that don't track the type
of corresponding major variables.
-
When you are writing code you don't need to constantly be quite so aware
of the precise types of each variable.
Why Not?
-
It makes it harder to add new types and conversion functions. Any such
won't be built-in..
Symmetrical Then/Else
In Bali, the if is more like Eiffel's. The C-style if is deprecated, but
still supported. In Java you might write:
if ((a=b==c)||(d==e)){f=g; r=s;}
else {f=h; r=t;}
In Bali you would write that as:
if (a = b == c) || (d == e)
then f = g; r=s;
else f = h; r=t; fi;
Why?
-
the then and else are more symmetrical. The true and false actions align
which enhances comparing their similarities and differences.
-
it is easier for a human to pick out where the condition ends and the true
action clause starts.
-
it helps reduce the parenthesis () forest in the condition by one level.
-
it helps reduce the brace {} forest in the actions by one level.
Why Not?
-
then fi is more keystrokes than () { }.
-
fi is an ad hoc end for an if. There should be a consistent system where
every block type has a unique matching begin end pair. You can't very well
use that fi technique for rof, chtiws, esac, elihw, yrt, hctac.
Up Front Declares
You might write something like this:
dcl
maxCount : int,
s : String,
myObject : SomeUserClass;
Why?
-
In Java, you have to plough through a great string of symbols before you
find the name of the thing being defined.
-
In Java there is no target string you can search for to find declarations.
-
With this scheme, declarations align.
Why Not?
-
It is too fundamental a change.
-
It does not extend properly to allow definitions in the middle of expressions.
-
It takes more keystrokes.
-
The initialisation syntax is clumsy. It is handled separately from the
declaration.
-
Without changing the Java syntax, a visual editor could solve the problem
by displaying a series of declarations aligned, possibly with some detail
suppressed. Similarly a visual editor could bold face the identifiers being
defined to make them easier to pick out. Java's declarations are quite
a bit simpler than C and C++'s. Such drastic measures are not needed in
Java.
Simpler Switch Syntax
In Java you might write:
switch (x)
{
case 1: doSomething();
break;
case 2:
case 3: doSomethingElse();
break;
default:
doSomethingVeryElse();
doSomeMore();
}
In Bali you might write that as:
switch x
case 1 : doSomething();
case 2,4 : doSomethingElse();
default : doSomethingVeryElse();
doSomeMore();
endswitch;
A more conventional alternate might be:
switch x
{
case 1 : doSomething();
case 2,4 : doSomethingElse();
default : { doSomethingVeryElse();
doSomeMore(); } end default
} end switch;
Why?
-
you avoid the long enclosing { } that is so easy to get unbalanced and
so hard to parse by eye.
-
You avoid accidental fallthrough when you forget the break;
-
it is more parallel to the syntax of the Bali if/then/else. If can be thought
of as switch x case false: case true: endswitch;
Why Not?
-
It would break existing code, especially code that depends on default fallthrough.
-
Perhaps with GUIs to help format text, we should be going for a more vertical
format where the various cases appear in a table side by side.
Explicit Concatentation Operator
I suggest that + for concatentation be deprecated, and soon outlawed altogether.
In its place we will use |||.
Why?
-
In Java, + does double duty: addition and concatenation. + on an int sometimes
means add, sometimes concatentate, depending on what surrounds it. That
is just plain Mickey Mouse, not to mention hard to read and bug inducing.
Why Not?
-
It takes three keystrokes instead of one.
Move Corresponding
I propose something similar to the COBOL MOVE CORRESPONDING that cannot
be expressed in the usual linear language syntax. It requires dialog boxes.
A MOVE CORRESPONDING should display a dialog box with a list of fields
that have matching names in object a and b. Beside each name
is a little checkbox that can have four states:
-
copy
-
ignore
-
use custom copying code. e.g.
switch (a.x) {
case 1: b.x = 10; break;
case 2: b.x = 15; break;
default: b.x = a.x; break; }
-
have not decided yet. (this generates a syntax error on compile).
Simply adding a new field to either a or b will create new
entries in all MOVE CORRESPONDING tables (marked not yet decided). The
editor also lets you view any leftover fields from a and/or leftover fields
from a.
Why?
-
Lists of fields copying from one object to another is common for versioning.
You rearrange fields, add fields and need to convert the old files to the
new format. In Java, SQL handles this problem automatically for rows. You
still have the problem of dealing with updating old objects or records
in flat files. You also have it when you export a file of some subset of
the information.
-
In my home-brew language Abundance, I found that a COBOL-style move corresponding
was NOT quite what was needed. I needed something of the form "MOVE CORRESPONDING
EXCEPT FOR ..." The other problem was maintenance. When you add new fields,
you need to examine all the move correspondings to figure out just what
should be done with the fields. It is easy to overlook them, because they
don't explicitly name the variables affected. My proposal lets you ensure
you have not forgotten to handle every field.
Why Not?
-
This form of MOVE CORRESPONDBING cannot be expressed without dialog boxes
that dynamically examine the lists of fields in the two objects.
Primitive Object Methods
In an early version of Java, primitives were objects. This made the language
simpler, but turned out to have too much overhead. It may be possible to
bring back part of this notion. For example, I think you should be able
to write y = x.sin() instead of y = Math.sin(x), and i.toString() instead
of Integer.toString(i).
Why?
-
The new notation avoids mentioning a class that has nothing to do with
the operation. In Integer.toString(i) there is no Integer object involved.
It is confusing to use a notation that implies there is.
-
The notation is consistent with the way you handle objects. Why should
there be different notations to do the same thing?
-
It may open the door to primitives even more like true objects, where you
can create subclasses of integers with type safety checking.
Why Not?
-
You still could not define your own methods to act on primitives as objects.
Until there was a way to subclass from primitives, the notation would only
be useful for builtin methods.
-
It is good to have separate notations for methods on objects and primitives.
It helps make the distinction between objects and primitives clear.
Short constant names
In Java you must write:
MyClass x = new MyClass();
x.doSomething(MyClass.ACONSTANT1, MyClass.ACONSTANT2);
In Bali, you could abbreviate that:
MyClass x = new MyClass();
x.doSomething(ACONSTANT1, ACONSTANT2);
The MyClass prefix is used to help define unknown constants mentioned between
the ( ) surrounding the parameters invoking a method of a class.
Why?
-
Saves typing the class name over and over.
Why Not?
-
There are conditions where the name ACONSTANT2 could be ambiguous. It may
be better to disambiguate it always, rather just than when it is needed.
-
Extending the scope of a class to parameter calls to its methods is just
too weird.
Spaciousness
I suggest the following simplification to the compiler's parser (the tokeniser
actually). All operands and operators must be separated by a space, with
the following exceptions: comma, unary +, unary -, ++, --, ., ;, (), []
In Java you might write:
x=a++--b*-(c++<<1);
In Bali, you would have to write that as:
x = a++ - -b * -(c++ << 1);
Why?
-
it gets rid of potentially ambigous (to humans) strings of + and -.
-
it makes code more readable.
-
it speeds compilation.
-
it opens the door to Forth/Abundance-like user defined operators and methods
with almost arbitrary names. You could use alpha, numbers, any punctuation,
any Unicode chars except ()[]+-,.space,;
Why Not?
-
It takes up more screen space and takes more keystrokes.
-
Cramming would be a very hard habit to break.
Divide and Modulus on Negative Numbers
If you divide two numbers, you can do four things with the result:
-
round - useful for approximating.
-
ceil - useful to calculate how many bins are needed to hold N items.
-
floor - useful to calculate which bin an item falls into when each bin
can hold N items.
-
trunc - Java style division.
Signs |
Java division |
Bali Division |
Java Modulus |
Bali Modulus |
+ + |
+7/+4=+1 |
+7/+4=+1 |
+7%+4=+3 |
+7%+4=+3 |
- + |
-7/+4=-1 |
-7/+4=-2 |
-7%+4=-3 |
-7%+4=+1 |
+ - |
+7/-4=-1 |
+7/-4=-2 |
+7%-4=+3 |
+7%-4=-1 |
- - |
-7/-4=+1 |
-7/-4=+1 |
-7%-4=-3 |
-7%-4=-3 |
Bali uses floored division. Bali takes the next lowest integer if the quotient
is fractional. The remainder has same sign as divisor. The absolute value
of the remainder is always less than the divisor.
Why?
-
Bali division is so defined that if you take the quotient and multiply
it by the divisor and add back the remainder you will get the dividend.
Surprisingly Java as it stands does not have this Euclidean property.
-
Every language I have encountered defines the way integer and modulus work
for negative numbers in a different way. The only one I found useful in
practice was Forth's floored approach. In every other language I found
myself handling negative cases specially doing the arithmetic on the absolute
values. In BigDate you can see examples where floored divide/modulus would
simplify code.
Why Not?
-
The problem with the approach is hardware usually does not work this way.
Implementing this convention in software would slow down code for the usual
all positive case.
-
You can't change rules like that in midstream. It may introduce subtle
bugs in existing code.
Uniform Inheritance Rules
In standard Java, the rules of inheritance and overriding depend on both
static/instance and variable/method. Methods override, variables shadow.
In Bali, the rules are more uniform. Methods override, but you
must declare them as overriding to ensure you don't do it by accident
(e.g. if the base class later adds a clashing method.) Constants
override. Variables may not be overridden or shadowed. It just causes
confusion. In Java, if you intend to override a method, and get the
signature slightly different, or slightly mis-spell the method name,
the compiler will not warn you. Your new method will just be
effectively ignored. This is particularly a problem in overriding
methods in adapter classes. In Bali, if your specify
override and there is no matching method to override, the
compiler will warn you.
You might write something like this:
class X extends Y
{
override const int AnomalyYear = 1582;
override public int getMonth () { return month + 1; }
original public int getMoonPhase() { ... }
} // end class X
Why?
-
Lets you create a subclass, reconfiguring the tweaking constants of the
base class. If you use the base class you get the original tweakers, if
the new class, the new tweakers.
-
You can't accidentally override or shadow.
-
Rules are more uniform. Confusion over the existing rules are a source
of bugs.
Why Not?
-
You can't fiddle with something this basic to a language.
-
The explicit override and original declarations just clutter the code.
They won't help safety, because most programmers will not bother to declare
either way.
-
Final currently means both "do not override" and "no changes to the value
after definition." This change requires a redefinition of the meaning of
final. Final would then mean "do not override". The new keyword const would
mean "no changes to the value after definition." const implies static.
-
Stealing const for this purpose may block other more imaginative uses of
the keyword in future.
-
Shadowing is just an extension of the local variable principle. If you
get rid of it you destroy encapsulation. Changes to one module force changes
to an unrelated one.
Sort Interface
In Abundance, you can sort an array or file with a piece of code like this:
BY DESCENDING Salary ASCENDING Hire-Date SORT
Abundance uses postfix notation. You don't even need to specify the
name of the array or file, since the compiler can easily deduce that. I
would like something similar for Bali like this:
HeapSort.sort(anArray, desc salary asc hireDate);
As it is, among several other things, you need to compose a new delegate
class with a compare routine something like this:
HeapSort.sort(anArray, new BySalaryAndHireDateCompare());
...
class BySalaryAndHireDateCompare implements Compare
{
/**
* return positive number if a > b, 0 if a = b, negative number if a < b
*/
int compare (Object a, Object b)
{
// compare descending salary, ascending hireDate Employee empa = (Employee) a;
Employee empb = (Employee) b;
if (empa.salary > empb.salary)
return -1;
if (empa.salary < empb.salary)
return +1;
return empa.hireDate - empb.hireDate;
}// end method compare
} // end class BySalaryAndHireDateCompare
Why
-
The current code is voluminous, hard to write, and even harder to proofread.
Changing the sort keys should be a trivial maintenance task.
-
The current technique suffers from name pollution.
-
By treating multi-key compares as special case, it may be possible to generate
more efficient code for the three way split on the compare result for each
key. It might be possible to avoid the heavy overhead of the two cast checks
on each compare.
-
Even COBOL does better than Java.
Why Not?
-
A sort statement just does not fit into the Java model. Even for a single
object, you might need several different compare routines.
-
RadixSort needs a quite different sort of compare routine.
-
It would force a standard interface on all sort routines.
-
You could get the same effect with a smart editor that generates the compare
code for you.
-
In Abundance each of the 50 primitive types has a standard compare routine.
Java has no equivalent. You can't tell from a Java String declaration how
case should be considered in comparing, how accented characters should
be collated etc. Since the String class is final, you can't create such
distinctions with subclassing.
-
The feature should be more general than just for an array sort. Therefore
the type needs to be specified explicitly. e.g. comparing Employee
desc salary asc hireDate.
-
To use this syntax you would need the collating fields to be public.
-
The new syntax should also allow functions as collating fields.