I’ve mentioned some of the cool stuff in the Oxygene language in various posts and thought it would be a good idea to list them again, along with some others that I’ve not previously mentioned.
First some of the core language features that are available on all supported platforms – Java, Cocoa and .NET
- Property Members
- Property Read Expressions
- Properties with Different Read/Write Protections
- Sophisticated Class Member Scoping
- Extended Constructor Calls (Create and Initialise)
- Case Statements with Strings
- In-line Variable Declarations
- Variable Type Inferencing
- Colon Operator
If you have a simple property which directly exposes a member variable you do not need to formally declare the member variable and the accessor/mutator properties, you can just declare the member variable as a property:
Rectangle = class public property Width: Integer; property Height: Integer; end;
Property Read Expressions
If you have a property whose value can be expressed using other members of the class you don’t need to wrap this up inside a formally declared accessor method, you can just write the expression directly in the property declaration:
Rectangle = class .. property Area: Integer read Width * Height; end;
Combining property members and read expressions allows us to write a simple rectangle class very cleanly, without any implementation code required at all:
Rectangle = class public property Width: Integer; property Height: Integer; property Area: Integer read Width * Height; end;
Which compares very favourably indeed with grand-daddy’s Pascal:
TRectangle = class private fWidth: Integer; fHeight: Integer; function get_Area: Integer; public property Width: Integer read fWidth write fWidth; property Height: Integer read fHeight write fHeight; property Area: Integer read get_Area; end; function TRectangle.get_Area: Integer; begin result := fWidth * fHeight; end;
Properties with Different Read/Write Protections
Ever had a property which was read-only for public access but which you wanted to make writable for private or protected use ? With Oxygene you can. In this case however, you do need to declare the accessor/mutator methods or fields. The property has a default visibility “inherited” from the visibility specified applying to the declaration, but this can be “overridden” on the read/write accessors.
So to create a public readable property with protected write access:
Rectangle = class private fWidth: Integer; fHeight: Integer; public property Width: Integer read fWidth protected write fWidth; property Height: Integer read fHeight protected write fHeight; property Area: Integer read Width * Height; end;
Sophisticated Class Member Scoping
Oxygene has some very expressive capabilities in the area of member scoping.
In addition to the usual private, protected, public, strict private, strict protected etc, Oxygene also allows assembly (corresponding to “package” in Java) and unit scoping.
The possible combinations are too numerous to mention here so for a full breakdown I refer you to this page of the Oxygene Language Wiki.
Suffice to say that the ability to directly express (and enforce) that a member be accessible to only those classes in a unit which are also sub-classes of the class or, alternatively, accessible to classes in a unit and to any sub-classes (to name just two possibilites) is very useful.
Anywhere that an expression may be used, an if-expression can be employed as an inline expression selector so instead of:
if useRed then view.TextColor := Color.RED else view.TextColor := Color.WHITE;
We can write:
view.TextColor := if useRed then Color.RED else Color.WHITE;
Extended Constructor Calls
Very often in OO we write code that consists of creating some object then setting some properties on that object:
myButton := new Button(self); myButton.Caption := 'OK'; myButton.Width := 100; myButton.Height := 25;
To simplify such cases in Oxygene, property assignments can be appended to the parameter list of any supported constructor. The initial parameters will be used to identify and call the appropriate constructor before applying the additional assignment expressions:
myButton := new Button(self, Caption := 'OK', Width := 100, Height := 25);
Case Statements with Strings
Oxygene case statements have much broader application than in Delphi. The most notable difference being that they support string selectors:
case aIntent.Action of 'nz.co.deltics.SCREEN_ON' : ... ; 'nz.co.deltics.SCREEN_OFF' : ... ; else ... end;
I seem to recall in an early version of Oxygene (what was then known as “Chrome”), the else clause of a case statement required a ‘begin’. It was arguably more syntactically correct according to a strict application of core Pascal syntax, but it was a bit cumbersome and somewhere along the way this seems to have changed.
Inline Variable Declaration
Variables can be declared in the usual Pascal manner in Oxygene, within a var block preceding the begin statement of a method. Or you can declare a variable in-line at first use. In-line declarations have the scope of their containing statement block.
if bSomeCondition then begin var i: Integer; ... do some work with an Integer, i end else begin var i: Intent; ... do some work with an Intent, i end; if NOT assigned(i) then // Compilation fails. "i" does not exist EXIT;
Inline Variable Type Inference
Most often when declaring a variable inline, it will be immediately initialised. In such cases Oxygene does not require that you declare the variable type. It will infer the appropriate type from the assignment expression. So the above in-line declaration example with some assumed initialisation, might look like this
if bSomeCondition then begin var i := 42; ... do some work with an Integer, i end else begin var i := new Intent(Intent.ACTION_POWER_CONNECTED); ... do some work with an Intent, i end;
The colon operator is a variation on the dot operator. Unlike the dot operator however, the colon operator offers some protection against potentially nil references. This can make certain programming tasks far easier.
Consider this hypothetical example of looking up some user object by name, obtaining the details object for that user and a company object within it and then extracting the name of that company, where there may not be any such user, the user may not have any details and the details if provided may not identify a particular company.
In terms of runtime performance, the most efficient way to implement this would be:
sCompanyName := ''; user := fUsers.getUserByName(aUserName); if Assigned(user) then begin var details := user.Details; if Assigned(details) then begin var company := details.Company; if Assigned(company) then sCompanyName := company.Name; end; end;
This example takes advantage of in-line, scoped variable declarations and type inferencing to improve things at least as far as possible. The equivalent in Delphi would be even more messy.
It is however unlikely that you would find such attention being paid to runtime efficiency given the aversion to “premature optimisation” and unnecessary typing that seems so prevalent these days. That, and the lack of in-line variable declarations and type inferencing means that you would be far more likely to encounter something like:
user := fUsers.getUserByName(aUserName); if Assigned(user) and Assigned(user.Details) and Assigned(user.Details.Company) then sCompanyName := user.Details.Company.Name else sCompanyName := '';
You will notice that in the case where there is a valid Company.Name to be retrieved, the above code evaluates user.Details no less than 3 (three!) times. If you are lucky, neither Details nor Company will be relatively expensive property accessor functions. Of course, they might not be when this code is first written, but that could change.
The Oxygene colon operator allows us to write code that at runtime is exactly equivalent to that first example however, but to do it in a single line of code:
sCompanyName := fUsers.getUserByName(aUserName):Details:Company:Name;
Not Your Grand-Daddy’s Java
So much for these additions to Pascal. However, there are also some things that we are well used to in Pascal – especially Delphi – that at first blush at least appear to be missing in Java.
Fortunately, Oxygene can ease the transition by adding support for some of these itself.
- get/set Methods as Properties
- Class References
- Virtual Class Methods and Constructors
- By-Reference Parameters and Records
get/set Methods as Properties
Java has no formal concept of properties. Instead the language has adopted a pattern based approach adopted from Java Beans, settling on the use of getPropertyName() and setPropertyName().
Oxygene recognises this pattern and exposes methods (which fit) as properties.
var i := new Intent; i.Action := Intent.ACTION_POWER_CONNECTED: // i.setAction( Intent.ACTION_POWER_CONNECTED );
Not all such methods fit this pattern. If a
get..() method has a void result (does not return a value) for example, then it is not a property accessor and cannot be treated as such.
This is purely syntactic sugar, however. You can still use the formal Java methods directly if you wish (or if the parameter lists do not suit a property style invocation).
Java has a Class type and every Object has a Class member which exposes the underlying Class type. But (from what I understand) this is not quite as capable* as Delphi’s class references concept, at least not without a great deal more work on the part of the developer.
*: In one area, Java class types serve a purpose which Oxygene class references do not (currently?) appear to. That is, to serve as class identities, as discussed in this stackoverflow question.
Fortunately Oxygene implements Delphi-like class references for us as well.
Consider the following class with two constructors, one parameterless and one accepting an integer parameter:
type Foo = public class constructor; constructor(aID: Integer); end; FooClass = class of Foo;
Using the FooClass class type, the declaration of which will be familiar to any Delphi developer, we can do everything we would expect. We can declare class reference parameters and use those references to create instances of the referenced class using any of it’s supported constructors:
method Bar.createFoo(aFooClass: FooClass; aID: Integer): Foo; begin result := new aFooClass(aID); end;
This is possible in Java (it must be, given that Oxygene compiles to the JVM byte code) but is far more complex, involving reflection if you need to invoke anything other than parameterless constructors.
I suspect that the Oxygene class reference implementation is founded on these underlying capabilities of Java, but surfaces them in a far more usable fashion!
var fc: FooClass; jc: &Class<Foo>; f: Foo; begin fc := FooClass; f := new fc(42); // Two ways of obtaining the Java Class type, from a class and from an object: jc := typeOf(Foo); // equivalent of ' = Foo.Class' in Java jc := f.&Class; // Instantiating a new class using Java Class type: f := jc.newInstance; f.setID(42); end;
Note: Since the Java Class mechanism uses
Class as an identifier, thus clashing with the class keyword in Oxygene, use of the Java
Class identifier has to be qualified with the identifier prefix ‘&‘, just as we would in Delphi in such cases.
Virtual Class Methods, Including Constructors
The real power of class references in Delphi stems from combining them with virtual class methods and virtual constructors. And yes, by some wizardry, these too are supported by Oxygene for Java !
Given these hypothetical declarations of an Employee and a Person class:
Person = public class public constructor; virtual; class method getMessage: String; virtual; end; PersonClass = class of Person; Employee = public class(Person) public constructor; override; class method getMessage: String; override; end;
The details of the
constructors and the
getMessage methods aren’t important, other than that the Employee constructor must obviously call
inherited while in the case of
getMessage this is not important and we simply return a different string from the version of this method on each class allowing us to easily determine which class was invoked.
We get the expected results from this sort of code:
var fc: FooClass; f: Foo; s: String; begin fc := Bar; f := new fc; s := f.Class.Name; // yields = 'Bar' s := f.getMessage; // calls Bar.getMessage end;
One thing to note here however is that type inferencing does not work for variables that are class reference types. I am not sure if this is by design, an unavoidable consequence of the implementation details, or whether it is simply a bug. Either way, it’s not a huge problem.
I have not looked to see how a class reference or virtual constructors such things appear to a Java developer consuming an Oxygene class.
With A Little Help From My Friends
There are a couple of further things that Oxygene adds to Java but which rely on an additional RTL support package (a jar file) called com.remobjects.oxygene.rtl.
By-Reference Parameters and Records
In Java, everything passed as a parameter to a method call is always, always passed by value. There are no by reference parameters in Java (var parameters in Pascal/Delphi speak).
Unless you are using Oxygene.
BatteryUtil = public class public method getBatteryInfo(var aPercent: Integer; var aCharging: Boolean); end;
If you use var parameters and your project does not reference the required RTL library you will be prompted to add it by a compiler error.
You will get a similar prompt to add the same library (if you are not using it) if you use another language feature that you may be used to in Pascal/Delphi: records.
Java has no specific support for record types or (structs, if you prefer). But Oxygene does. Again, via the RTL support library.
Is There NOTHING That Oxygene Can’t Do ?
It might seem like that, but actually there is (at least) one thing.
If you look at the extensions to the Java language capabilities that Oxygene provides, they all rely on encapsulating existing techniques in the JVM for achieving things that other languages support by first class means, elevating these capabilities to first class status in Oxygene itself, without breaking the fact that underneath it all sits Java itself.
Ironically, there is (at least) one area where Java is so much more capable than the Pascal language the an existing first-class capability of Pascal appears not to be so easily portable to Java.
Enums can be declared just as in Pascal, but set type declarations are not supported.
The only reason I can think of for this omission is that Java already has comprehensive support for sets in it’s collections support. As such there is perhaps not one, definitive, “set” type onto which a simple “set of” declaration can be usefully mapped. Any Java class that an Oxygene developer may need to work with might have chosen some other set type.
Perhaps RemObjects have in mind to provide a more versatile syntax to surface the Java set types in the language and are holding off on implementing this until they are sure they have a comprehensive solution. Or perhaps they simply feel there is no need.
Either way, in the meantime declaring a set using the Java framework classes is not much different than a first class “set type” would provide. You will also not encounter any Pascal/Oxygene sets in any Java classes you work with, so the omission is not likely to pose a problem, other than if you wished to use a Pascal set type in some cross-platform, portable Oxygene code. On that score, the NSSet (and NSMutableSet) type on Cocoa is similarly quite different to the Pascal type, again making a language-level mapping less useful than simply providing first-class access to the platform framework capabilities.
Comments are now closed.