Jump to content

Oxygene (programming language)

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by CoreCoreCore (talk | contribs) at 18:25, 27 June 2010 (Oxygene Language Syntax). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Oxygene
DeveloperRemObjects Software
Stable release
3.0.21 / August 29, 2009 (2009-08-29)
OSCommon Language Infrastructure
LicenseCommercial
WebsiteOxygene Homepage
Influenced by
Object Pascal, C#

Oxygene (formerly known as Chrome) is a programming language developed by RemObjects Software for the Common Language Infrastructure. Oxygene is Object Pascal-based. Compared to the now deprecated Delphi.NET, Oxygene does not emphasize total backward compatibility, but is designed to be a full .NET citizen and leverage all the features and technologies provided by the .NET runtime.

Starting 2008, RemObjects Software has licensed its compiler and IDE technology to Embarcadero to be used in their Delphi Prism product.[1] Delphi Prism offers full integration into Visual Studio 2005/2008. If the user doesn't have Visual Studio installed, Oxygene's setup includes the Visual Studio 2008 Shell.

Oxygene Language

The Oxygene language has its origins in Object Pascal in general and Delphi in particular, but was designed to reflect the guidelines of .NET programming and to create fully CLR-compliant assemblies. Therefore not all language features known from Object Pascal / Delphi can be found any longer in Oxygene or only as legacy features.

Object Oriented Programming

Oxygene is an object-oriented language, which means it uses classes, which can hold data and execute code, to design programs. Classes are "prototypes" for objects, like the idea of an apple is the prototype for the apple you can actually buy in the shop. You know, that an apple has a color and you know, that it can be peeled: that are is data and executable "code" for the apple class.

Parallel Programming

Oxygene provides language-level support for some of features of parallel programming. The goal is to use all cores or processors of a computer to improve performance. To reach this goal, tasks have to be distributed among several threads. The .NET framework's ThreadPool class offered a way to efficiently work with several threads. The Task Parallel Library (TPL) was introduced in .NET 4.0 to provide more features for parallel programming.

Operator Overloading

Operators can be overloaded in Oxygene using the class operator syntax:

class operator implicit(i : Integer) : MyClass;

Note, that for operator overloading each operator has a name, that has to be used in the operator overloading syntax, because for example "+" would not be a valid method name in Oxygene[2].

Program Structure

Oxygene does not use "Units" like Delphi does, but uses .NET-namespaces to organize and group types. A namespace can span multiple files (and assemblies), but one file can only contain types of one namespace. This namespace is defined at the very top of the file:

namespace ConsoleApplication1;

Oxygene files are separated into an interface and an implementation section, which is the structure known from Delphi. The interface section follows the declaration of the namespace. It contains the uses-clause, which in Oxygene imports types from other namespaces:

uses 
  System.Linq;

Imported namespaces have to be in the project itself or in referenced assemblies. Unlike in C#, in Oxygene you cannot define alias names for namespaces, only for single type names (see below).

Following the uses-clause a file contains type declarations, like they are known from Delphi:

interface

type
  ConsoleApp = class
  public
    class method Main;
  end;

As in C#, the Main-method is the entry point for every program. It can have a parameter args : Array of String for passing command line arguments to the program.

More types can be declared without repeating the type-keyword.

The implementation of the declared methods is places in the implementation section:

implementation

class method ConsoleApp.Main;
begin
  // add your own code here
  Console.WriteLine('Hello World.');
end;

end.

Files are always ended with end.

Examples of Oxygene .NET

Types

As a .NET language, Oxygene uses the .NET type system: There're value types (like structs) and reference types (like arrays or classes).

Although it does not introduce own "pre-defined" types, Oxygene offers more "pascalish" generic names for some of them[3], so that for example the System.Int32 can be used as Integer and Boolean (System.Boolean), Char (System.Char), Real (System.Double) join the family of pascal-typenames, too. The struct character of these types, which is part of .NET, is fully preserved.

Type Visibility

As in all .NET languages types in Oxygene have a visibility. In Oxygene the default visibility is assembly, which is equivalent to the internal visibility in C#. The other possible type visibility is public.

type
  MyClass = public class
  end;

The visibility can be set for every type you define (classes, interfaces, records, ...).

Type Aliases

You can define an alias name for types, which can be used locally or in other Oxygene-assemblies, too.

type
  IntList = public List<Integer>; //visible in other Oxygene-assemblies
  SecretEnumerable = IEnumerable<String>; //not visible in other assemblies

Public type aliases won't be visible for other languages.

Value Types
Records

Records are what .NET-structs are called in Oxygene. They are declared just like classes, but with the record keyword:

type
  MyRecord = record
    method Foo;
  end;

As they're just .NET-structs, records can have fields, methods and properties, but do not have inheritance and cannot implement interfaces.

Interfaces

Interfaces are very important concept in the .NET world, the framework itself makes heavy use of them. Interfaces are the specification of a small set of methods, properties and events a class has to implement when implementing the interface. For example contains the interface IEnumerable<T> specifies the GetEnumerator method which is used to iterate over sequences.

Interfaces are declared just like classes:

type
  MyInterface = public interface
    method MakeItSo : IEnumerable;
    property Bar : String read write;
  end;

Please notice, that for properties the getter and setter are not explicitly specified.

Delegates

Delegates define signatures for methods, so that these methods can be passed in parameters (e.g. callbacks) or stored in variables, etc. They're the type-safe NET-equivalent to function pointers. They're also used in events. When assigning a method to a delegate, one has to use the @ operator, so the compiler knows, that one doesn't want to call the method but just assign it.

Oxygene can create anonymous delegates, so for example you can pass methods to the Invoke method of a control without declaring the delegate:

method MainForm.MainForm_Load(sender: System.Object; e: System.EventArgs);
begin
  Invoke(@DoSomething);
end;

An anonymous delegate with the signature of the method DoSomething will be created by the compiler.

Oxygene supports polymorphic delegates, which means, that delegates which have parameters of descending types are assignment compatible. Assume two classes MyClass and MyClassEx = class(MyClass), then in the following code BlubbEx is assignment compatible to Blubb.

type
  delegate Blubb(sender : Object; m : MyClass);
  delegate BlubbEx(sender : Object; mx : MyClassEx);
Delegating Interface Implementation

Fields can be used to delegate the implementation of an interface, if the type they're of implements this interface:

Implementor = public class(IMyInterface)
  // ... implement interface ...
end;

MyClass = public class(IMyInterface)
  fSomeImplementor : Implementor; public implements IMyInterface; //takes care of implementing the interface
end;

In this example the compiler will create public methods and properties in MyClass, which call the methods / properties of fSomeImplementor, to implement the members of IMyInterface. This can be used to provide mixin-like functionality[4].

Anonymous Methods

Anonymous methods are implemented inside other methods. They are not accessible outside of the method unless stored inside a delegate field. Anonymous methods can use the local variables of the method they're implemented in and the fields of the class they belong to.

Anonymous methods are especially useful when working with code that is supposed to be executed in an GUI thread, which is done in .NET by passing a method do the Invoke method (Control.Invoke in WinForms, Dispatcher.Invoke in WPF):

method Window1.PredictNearFuture;  //declared as async in the interface
begin
  // ... Calculate result here, store in variable "theFuture"
    Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, method; begin
      theFutureTextBox.Text := theFuture;
    end);
end;

Anonymous methods can have parameters, too:

method Window1.PredictNearFuture;  //declared as async in the interface
begin
  // ... Calculate result here, store in variable "theFuture"
    Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, method(aFuture : String); begin
      theFutureTextBox.Text := aFuture ;
    end, theFuture);
end;

Both source codes use anonymous delegates.

Property Notification

Property notification is used mainly for data binding, when the GUI has to know when the value of a property changes. The .NET framework provides the interfaces INotifyPropertyChanged and INotifyPropertyChanging (in .NET 3.5) for this purpose. These interfaces define events which have to be fired when a property is changed / was changed.

Oxygene provides the notify modifier, which can be used on properties. If this modifier is used, the compiler will add the interfaces to the class, implement them and create code to raise the events when the property changes / was changed.

property Foo : String read fFoo write SetFoo; notify;
property Bar : String; notfiy 'Blubb'; //will notify that property "Blubb" was changed instead of "Bar"

As you can see, the modifier can be used on properties which have a setter method. The code to raise the events will then be added to this method during compile time.

Language Features

New Language Features in Version 3.0

Parallel Programming

Oxygene 3.0 introduces a wide range of language concepts that push the envelope for parallel programming and enable developers to create applications that seamlessly scale up for multi-core and many-core systems. This includes support for Futures, Parallel Loops, Asynchronous Statements, an improved locked directive, and more.

Property Notifications

Native language support for property notifications makes it easy to develop solutions that follow the Model/View/Controller design pattern or generally react in well-defined ways to property changes.

Nullable Expressions

Oxygene's Expression syntax has been expanded to provide full support for nullable types in arithmetic and other expressions, making the language integration of nullables even more seamless than in 'Joyride'. Improvements have also been made for casting between compatible nullable types, such as assigning a nullbale Int32 to a nullable Int64, etc.

New Language Features in Version 2.0 ('Joyride')

Sequences and Query Expressions

Query Expressions, also known as "Language Integrated Query", or LINQ for short, are a powerful new language feature that allows you to combine the querying capabilities of database languages such as SQL and apply it to any type of data, natively within the Oxygene language.

Queries can be written to work on any type of structured collection, from simple lists and arrays to database tables and other data structures. Combined with the DLinq and XLinq libraries provided by Microsoft as part of the .NET 3.5 runtime, the feature can be used to efficiently query data from database sources and XML documents, without actually retrieving the entire set of data into memory.

var u := from u in lUsers
           where u.Age = 35
           order by u.Name;

Lambda Expressions

Mostly used alongside LINQ and query support, lambda expressions provide a unique syntax for passing dynamic expressions as part of method parameters.

var lFiltered := lUsers.Where(u -> u.Age = 35);

Anonymous Types

Once again mostly used in LINQ expressions, anonymous types allow you quickly declare unnamed classes within a method body, to group related values in a common entity.

var lUser := new class(Name := 'Peter'; Age := 49);
Console.WriteLine(lUser.Name+' is '+lUser.Age+' years old');

Partial Methods

New Partial Method support allows you to define a stub for a method in one part of a class, allowing an optional implementation to be provided by another partial. If no implementation is provided, the method and any calls to it will be omitted from the generated assembly.

Partial Methods make it easy for auto-generated code to define methods that can be implemented by the user. This is used extensively in our new Cocoa# support, as well as upcoming support for LINQ to SQL.

Enhanced Nullable Types

'Joyride' enhanced support for nullable types, making them a full language feature that fits in neatly with the other types rather than having nullable types feel like a runtime trick. For example, variables defined as nullable Int32 allow full access to members of Int32 and behave like a true Int32 in every sense - with the addition of allowing nil values.

In combination with the new ":" operator (described below) and the newly introduced ValueOrDefault() helper function, nullable types are now easier to use than ever, and feel more natural than in any other .NET language.

Extension Methods

Extension methods are a feature introduced by Microsoft in the .NET 3.5 runtime to support LINQ, but can be used in Oxygene in a wide variety of scenarios and on all framework versions.

Simply put, Extension Methods are methods declared in a static class that extend an existing class or interface, and can be invoked on a variable of that type. For example the Where extension method provided by .NET 3.5 extends IEnumerable<T>, and thus can be used on any sequence of objects to filter the collection on an arbitrary condition, even though IEnumerable<T> does not provide a Where member:

var lUsers := array of User;
//...
var lPauls := lUsers.Where(u -> u.Name = 'Paul');

Anonymous Methods

Anonymous methods make it possible to specify code assigned to event handlers or passed to delegates right within the body of another method. They not only allow you to skip manually declaring a method within the class, but they also seamlessly allow access to any local variables available within the context where the anonymous method is written.

method SetEventHandler;
begin
  var lNewCaption := 'Clicked!';
  Button1.Click += method(Sender: Object; ea: EventArgs); begin
      Button1.Text := lNewCaption
      MessageBox.Show('the button was clicked.');
    end;
end;

Colon ':' Operator

'Joyride' introduces a new operator that can be used anyplace the familiar "." can is used, be it to access a method, properties or other members of a type. Different from the "." operator, which requires the value on its left to be assigned (and will usually throw a NullReferenceException if it is not), the new ":" operator will allow to call members on any value, including nil. If the expression on the left of the colon is nil, the entire expression will automatically short-circuit to return nil as well.

This makes it very easy to access nested members in object hierarchies, when multiple if assigned() checks would otherwise be needed. (BLOG)

var lGreatGrandDadsName := lUser:Parent:Parent:Parent:Name;

'Params' Keyword

The params keyword makes it easy to define methods that take a variable number of arguments. By closing the list of parameters with an array parameter prefixed with the params keyword, you can enable callers of your method to pass variable numbers of elements to method calls, which will automatically be converted into an array. This makes it easier to call the method, especially from languages like C#, where constructing an inline array is long and unwieldy.

method Format(aFormat: string: params values: array of Int32);
//...
Format('...', 1, 2, 3, 5, 27);

'implies' Operator

The new implies operator was designed specifically for require/ensure clauses and class invariants, but can also be used elsewhere in code. Similar to and or, it combines two boolean expressions; the difference is that with implies, the second expression is only evaluated if the first/left expression is true. if the first expression is false, the entire expression will be considered true.

ensure
  HasDriversLicense implies Age >= 16;

Type Inference for 'for each' Loops on Generic Sequences

For 'Joyride', for each loops have been improved to automatically infer the type of the loop variable when working on a generic IEnumerable<T> or other strongly-typed sequence, avoiding the need to manually specify the type name. As a side effect, for each loops will now always implicitly define their loop variable.

var lUsers: array of Users;
for each u in Users do
  Console.WriteLine(u.Name); // Compiler knows "u" is a "User"

For non-generic enumerations (IEnumerable), a explicit type declaration will be required inside the for each loop; the compiler will not automatically infer to use System.Object.

'index' Operator for 'for each' Loops

The Syntax for each loops has been expanded in 'Joyride' to allow for an optional index variable to be defined, which will count from 0 through the number of elements looped. This is helpful in scenarios where the number of elements processed is needed as part of the loop, be it to access a separate collection by index, or to use different code to handle the first or even/odd elements.

When using for each matching or other mechanisms (such as LINQ) to filter down the collection, the index will only count those elements that actually execute the loop.

for each u in Users index i do begin
  if i > 0 then Console.Write(';');
  Console.Write(u.Name);
end;

New Language Features in Version 1.5 ('Floorshow')

Generic Methods

Use Generics to implement strongly typed methods with and parameterized types (.NET 2.0 only).

Iterators

Easily implement collections and enumerable classes using iterators for both .NET 1.1 and 2.0 using the new 'iterator' directive and the 'yield' keyword.

method CountTo100: Int32; iterator;
begin
  for i: Int32 := 0 to 100 do yield i;
end;

Nullable Types

Avoid boxing by using new nullable value types on the .NET 2.0 framework. Support for nullable types has been vastly enhanced for 'Joyride' (see above).

var i := nullable Int32;
if assigned(i) then i.CompareTo(5);

Nested Types

Define and implement nested types using Oxygene's new and intuitive 'nested in' syntax

type
  HelperClass nested in MainClass = class //...

Dual-visibility for Properties

Define properties with different visibility levels for getter and setter, for example allowing public reading and protected writing of properties:

property Count: Int32 read fCount protected write SetCount;

Extended Constructor Calls

Create objects and initialize properties in a single statement

var b := new Button(Width := 150; Height := 25);

Fixed Size Buffers

Use Fixed Size Buffers to declare efficient inline arrays inside your records, in "unsafe" code.


New Language Features over Object Pascal in Version 1.0

Generic Types

Use Generics to implement strongly typed containers and parameterized types.

Class Contracts

Oxygene is the first mainstream .NET language to provide native support for Design By Contract like constructs, with pre-conditions, post-conditions and invariants.

Namespace Support

Namespaces are one of the great basic concepts of the .NET framework that most developers take for granted. Oxygene provides three basic features that allow developers to work with namespaces.

Virtual Properties

Virtual Properties and Events allow you to more easily define abstract classes and interfaces, or overwrite existing framework interfaces that contain properties.

Enhanced Events Support

Oxygene introduces a new syntax for defining and working with events to the Object Pascal language.

Asynchronous Methods and Thread Synchronization

Easily write multi-threaded applications using Oxygenes' async keyword and asynchronous methods. Use the locked and locking keywords to write thread-safe applications.

Partial Classes

The only .NET compiler to provide partial classes support for .NET 1.1.

Operator Overloading

Make your classes intuitive to use by providing custom operator overloads for common operations such as addition or subtraction.

Class References & Virtual Constructors

Easily implement the Factory Pattern or dynamically create object instances using Oxygene's Class References (Meta Classes).

Enhanced Loops

Oxygene enhances the classic for/to loop to allow inline declaration of the loop variable type. It also provides a new for each loop type for looping across .NET IEnumerable and IEnumerable<T> types, as well as an infinite loop loop.

Inline Variable Declarations and Type Inference

Declare new variables using the 'var' statement inside your method bodies to keep them with the code that uses them. Avoid retyping type names and let Oxygene infer new variable types from the assigned value.

Inline Property Readers

Use inline code to implement simple property readers, such as

property Foo: String read 'Bar';

Enhanced 'case of' and 'case type of' statements

'case' statements can use strings or other non-ordinal types, as well as the new 'case type of' statement to execute different cases depending of an object's type.

case aClassID.ToUpper of
   'XYZ': result := TMyXYZClass;
   'ABC': result := TMyOtherClass;
  else raise new Exception('Invalid Class ID');
end;

case aClass type of
   TMyXYZClass: TMyXYZClass(aClass).DoSomething;
   TMyOtherClass: TMyOtherClass(aClass).DoSomethingElse;
   else raise new Exception('Invalid Class Reference');
end;

Enhanced 'try/finally/except'

Combine 'finally' and 'except' to create more concise and efficient exception handling code.

Exception Filters

Previously only available in Visual Basic .NET, Exception Filters provide extended flexibility for catching exceptions over common 'try/except' blocks.

Boolean Double Comparisons

Easily compare values against boundaries with statements such as

if 0 <= x < Count then //...

Empty Methods

Quickly define class interfaces to flesh out later or empty methods to be overridden in descendant classes.

Static Classes

Implement static classes that cannot be instantiated at runtime, but provide static functionality to your project.

Enhanced 'exit' statement

Use the improved exit statement to terminate methods and set a return value in a single step.

Enhanced 'is not' and 'not in' statements

Use the new 'is not' operator to write more readable type check statements, and 'not in' for improved set handling.

Differences between native Delphi and Oxygene / Delphi Prism

Deprecated Keywords

  • unit: Replaced with the namespace keyword. Since Oxygene doesn't compile per-file but per-project, it does not depend on the name of the file. Instead the unit or namespace keyword is used to denote the default namespace that all types are defined in for that file
  • procedure and function: These two keywords have been replaced with the method keyword.
  • overload: In Delphi Prism all methods are overloaded by default, so no special keyword is needed for this.
  • .Create(): This constructor call has been replaced by the new keyword. It can still be enabled in the project options for legacy reasons.

Criticism

Some people would like to port their Win32 Delphi code to Prism as is. This is not possible because while Prism looks like Delphi there are enough changes to make it incompatible for a simple recompile. So while the name makes you think it is just another version of Delphi that is not completely true.[5]

On top of the language differences the Visual Component Library framework is not available in Delphi Prism.[6] This makes porting even more difficult because classic Delphi code relies heavily on the VCL.

Code examples

Hello World

namespace HelloWorld;
  
interface
 
type
  HelloClass = class
  public
    class method Main; 
  end;
  
implementation
 
class method HelloClass.Main;
begin
  System.Console.WriteLine('Hello World!');
end;
  
end.

Generic container

namespace GenericContainer;
 
interface
 
type
  TestApp = class
  public
    class method Main;
  end;
  
  Person = class
  public
    property FirstName: String;
    property LastName: String;      
  end;        

implementation

uses 
  System.Collections.Generic;

class method TestApp.Main;
begin
  var myList := new List<Person>; //type inference
  myList.Add(new Person(FirstName := 'John', LastName := 'Doe'));  
  myList.Add(new Person(FirstName := 'Jane', LastName := 'Doe'));
  myList.Add(new Person(FirstName := 'James', LastName := 'Doe'));  
  Console.WriteLine(myList[1].FirstName);  //No casting needed
  Console.ReadLine;        
end;

end.

Generic method

namespace GenericMethodTest;

interface

type
GenericMethodTest = static class
public
  class method Main;
private
  class method Swap<T>(var left, right : T);
  class method DoSwap<T>(left, right : T);
end;

implementation

class method GenericMethodTest.DoSwap<T>(left, right : T);
begin
  var a := left;
  var b := right;
  Console.WriteLine('Type: {0}', typeof(T));
  Console.WriteLine('-> a = {0}, b = {1}', a , b);
  Swap<T>(var a, var b);
  Console.WriteLine('-> a = {0}, b = {1}', a , b);
end;

class method GenericMethodTest.Main;
begin
  var a := 23;// type inference
  var b := 15;
  DoSwap<Integer>(a, b); // no downcasting to Object in this method.

  var aa := 'abc';// type inference
  var bb := 'def';
  DoSwap<String>(aa, bb); // no downcasting to Object in this method.

  DoSwap(1.1, 1.2); // type inference for generic parameters
  Console.ReadLine();
end;

class method GenericMethodTest.Swap<T>(var left, right : T);
begin
  var temp := left;
  left:= right;
  right := temp;
end;

end.

Program Output:

Type: System.Int32
-> a = 23, b = 15
-> a = 15, b = 23
Type: System.String
-> a = abc, b = def
-> a = def, b = abc
Type: System.Double
-> a = 1,1, b = 1,2
-> a = 1,2, b = 1,1

---

See also

References

  1. ^ [1] Embarcadero Delphi Prism page, at the bottom can be seen that they license the Oxygene Compiler.
  2. ^ http://prismwiki.codegear.com/en/Operator_Overloading
  3. ^ http://prismwiki.codegear.com/en/Built-In_Types
  4. ^ http://prismwiki.codegear.com/en/Provide_Mixin-like_functionality
  5. ^ [2] A Stack overflow discussion where people remark that Delphi Prism is not Delphi Win32.
  6. ^ [3] Delphi Prism 2010 review where they state in the third paragraph that VCL.net is not available.