L
lich
Unregistered / Unconfirmed
GUEST, unregistred user!
Chrome: Object Pascal 的一个变种, 简洁多了
Chrome是RemObjects推出的下一代Object Pascal语言,运行于.NET和Mono平台之上。在保留Object Pascal特性的同时,Chrome还借鉴了C#、Java、Eiffel等语言的优秀元素。
一种新的的语言诞生了:
C++ & Java -> C#
Object Pascal -> Chrome
namespace Chrome.Samples.SampleClasses;
interface
uses
System,
System.Collections;
type
SexEnum = (sxMale, sxFemale);
Person = class
private
fNickName: string;
protected
method SetNickName(Value : string);
public
constructor (aName : string; anAge : integer; aSex : SexEnum); virtual;
method GenerateSSN : string;
{ Notice how Chrome doesn't require the declaration of private fields to
store property values, thus making the code more compact and easier to read }
property Name : string;
property Age : integer;
property Sex : SexEnum;
property NickName : string read fNickName write SetNickName;
property Address : PersonAddress := new PersonAddress();
public invariants
{ These conditions are guaranteed to always be respected at the end of
the execution of a non-private method of the Person class }
Age >= 0;
Name <> '';
end;
PersonClass = class of Person;
PersonAddress = class
public
property City : string;
property Street : string;
property Zip : string;
end;
Employee = class(Person)
private
protected
method GetCurrency : double;
public
property Salary : double read GetCurrency;
end;
PersonCollection = class(CollectionBase)
private
protected
{ Type checking events }
procedure OnInsert(Index : integer; Value : Object); override;
procedure OnValidate(Value : Object); override;
{ Other methods }
function GetPersonsByIndex(anIndex : integer) : Person;
function GetPersonsByName(aName : string) : Person;
public
constructor;
method Add(aName : string; anAge : integer; aSex : SexEnum) : Person;
method Add(aPerson : Person) : integer;
method IndexOf(aPerson : Person) : integer;
procedure Remove(aPerson : Person);
{ Notice the use of overloaded array properties }
property Persons[anIndex : integer] : Person read GetPersonsByIndex; default;
property Persons[aName : string] : Person read GetPersonsByName; default;
end;
{ ECustomException }
ECustomException = class(Exception);
implementation
{ Person }
constructor Person(aName : string; anAge : integer; aSex : SexEnum);
begin
inherited Create;
Name := aName;
Age := anAge;
Sex := aSex;
end;
method Person.GenerateSSN: string;
var
lHashValue: integer;
begin
{ Generates a complex string with all the information we have and outputs a fake SSN
using the String.Format method }
lHashValue := (Age.ToString+'S'+Sex.ToString[2]+Name.GetHashCode.ToString).GetHashCode;
if lHashValue < 0 then lHashValue := -lHashValue;
result := String.Format('{0:000-00-0000}', lHashValue);
end;
method Person.SetNickName(Value : string);
require
Value <> '';
begin
fNickName := Value;
ensure
fNickName <> '';
end;
{ PersonCollection }
constructor PersonCollection;
begin
inherited;
end;
method PersonCollection.Add(aName : string; anAge : integer; aSex : SexEnum) : Person;
begin
result := new Person(aName, anAge, aSex);
List.Add(result);
end;
method PersonCollection.Add(aPerson : Person) : integer;
begin
result := List.Add(aPerson);
end;
method PersonCollection.IndexOf(aPerson : Person) : integer;
begin
result := List.IndexOf(aPerson);
end;
procedure PersonCollection.Remove(aPerson : Person);
begin
List.Remove(aPerson);
end;
function PersonCollection.GetPersonsByIndex(anIndex : integer) : Person;
begin
result := List[anIndex] as Person;
end;
function PersonCollection.GetPersonsByName(aName : string) : Person;
begin
for each somebody : Person in List do
if (String.Compare(aName, somebody.Name, TRUE)=0)
then Exit(somebody);
end;
procedure PersonCollection.OnInsert(Index : integer; Value : Object);
begin
OnValidate(Value);
end;
procedure PersonCollection.OnValidate(Value : Object);
begin
{ Notice the use of the "is not" syntax }
if (Value is not Person)
then raise new Exception('Not a Person');
end;
{ Employee }
method Employee.GetCurrency : double;
begin
case Age of
0..15 : Exit(0);
16..24 : Exit(15000);
25..45 : Exit(55000);
else exit(75000);
end;
end;
end.
CH03 - Generics in Chrome (Last updated 4/14/2005)
As part of our support for the upcoming Microsoft .NET Framework 2.0 (code-named Whidbey), Chrome provides full support for using and implementing the so called Generic Types.
What are Generics?
Generic types allow you to write strongly typed code that can later be used on a variety of different types. The classical example for efficient use of generics is container classes. Traditional class libraries (including the .NET Framework 1.1) usually implement containers by using a lowest common denominator class - for example System.Object. All the containers accept objects and will return objects and if you need lists of specific types (say, a custom class Foo), you will end up casting the elements obtained from your list, at runtime, as in this (contrived) example:
method Test;
var
lList: ArrayList := new ArrayList;
begin
lList.Add(new Foo);
lList.Add(5); // oops
//...
(lList[1] as Foo).Bar;
end;
Note how when accessing the element in the list, you need to cast the result back to a Foo manually before you can call its method. If for some reason a wrong type was added to the list (like the "5" that's added in the second line of code above). Your code will break with an invalid cast exception at runtime.
Instead, Generics allow you to write collections that are strongly typed to any given type. If ArrayList were a generic class, the code would look as follows and prevent any chance of type conflicts:
method GenericTest;
var
lList: ArrayList<Foo> := new ArrayList<Foo>;
begin
lList.Add(new Foo);
//lList.Add(5); // this will not compile
//...
lList[1].Bar; // no cast necessary, List[] is strongly typed
end;
The above example already shows you how to make use of existing generic classes from your Chrome code by using the new <> syntax to specify the concrete types (in this case Foo) with which you want to instantiate your generic class. The 2.0 Framework provides a wealth of reusable generic collections in the System.Collections.Generic namespace.
However, in addition to simply consuming existing classes, you can also use Chrome to implement your own generic classes, thus enabling users of your library to use them in a type-safe manner.
Implementing Generic Classes
As an example, let's implement a simple linked list as a generic class and look at the individual language features that are involved in making this work.
type
List<T> = public class
public
constructor(aData: T);
constructor(aData: T; aNext: List<T>
property Next: List<T>;
property Data: T;
method ToString: string; override;
end;
The above class declaration defines a typical node for a linked list; the node contains two properties (the data for the actual node and a link to the next node in the list). It also contains a couple of helper functions which we will implement later to perform common actions on the list.
Generic Parameters
The first thing that will strike you immediately as different from a "normal" class declaration is the use of "T" in several places, for example as the type for the Data property. What is this T? How is it related to the actual types we want to store in our list?
Simply put, T is a so called Generic Parameter of your list class and acts as a placeholder for the actual concrete types with which your list will later be instantiated. If your user eventually instantiates a List<Int32>, then all occurrences of T will represent an integer value; if he instantiates a List<Foo>, they will represent Foos.
Generic Parameters are defined in the first line of your class declaration (List<T> = public class) and can then be used thoughout your class declaration and implementation in any place where a type is expected - as method parameters or results, as property of field types, or in code that deals with types.
Even though this example only declares one generic parameter (T), you can write classes that declare as many parameters as needed and you can use any valid identifier as parameter names (although a common convention is to use uppercase letters starting with "T".
type
Dictionary<Key, Value> = public class ... end;
is a perfectly valid generic class.
Let's implement the ToString method and see how we can work with the Data and Next properties, just as if they were real concrete types:
method List<T>.ToString: string;
begin
result := Data.ToString;
if assigned(Next) then
result := result +';'+Next.ToString;
end;
Note how each node will simply call the ToString method on Data and then defer to the next node's ToString to build the rest of the result; eventually building a complete list of all values by passing over each and every item of the list.
Calling ToString is possible because it is a member of Object and thus available for any possible type T. But what if we need to call more advanced members on the generic elements in our List? That's where Generic Constraints come into play.
Generic Constraints
Chrome allows you to limit the types that will be eligible for a given generic parameter. Limiting the types for T allows your code to make certain assumptions about T and, as a result, perform certain actions on the types that would not be valid on "any" type.
Two types of constraints are available for the 20 framework, Constructor Constraints and Type Constraints.
The first constraint allows you to require the concrete types used to instantiate your generic class to provide a constructor that is without any parameters. This is necessary if, for whatever reasons, you need to call the constructor and create new instances of T in your class. Take for example the following addition to our List<T> class:
method List<T>.AddNew: T;
var
lEnd: List<T>;
begin
lEnd := self;
while assigned(lEnd.Next) do
lEnd := lEnd.Next;
lEnd.Next := new List<T>;
lEnd.Next.Data := new T;
end;
Note how the last line of code creates a new instance of T and stores it at the end of the list. This is only possible if we can rely on T having a simple constructor without parameters, so we need to specify the Constructor Constraint for our generic class, using the "T has constructor" statement in the generic's where clause:
type
List<T> = public class
where T has constructor;
public
//...
end;
Secondly, lets assume that you want to add a Sort method to your list, which will use your favorite sort algorithm (say, Random Sort) to print the elements in your list into order. This presents us with a similar problem as before: to sort the list, we must be able to compare the individual items, but how can you compare the items without knowing their type? The answer is simple: let the individual items provide their own logic for comparing themselves via the IComparable interface and set a Type Constraint on your class to require that any concrete type used to instantiate your generic class must implement this interface.
This is done by supplying the "T is IComparable" statement in the where clause:
type
List<T> = public class
where T has constructor, T is IComparable;
public
//...
end;
The where clause
The where clause for a generic type can list any number of constraints, separated by commas. You can require several interfaces to be implemented by any given generic parameter, or you can require a specific ancestor class by specifying the said class. You can also mix requirements for several parameters, e.g.:
type
Dictionary<Key, Value> = public class ...
where Key is IComparable, Key has constructor, Value is Component;
// ...
end;
which defines a generic Dictionary class where the Key type must be comparable (understandably) & able to be created and the Value type must be Component or a descendant.
Summary
We hope this article has given you a quick introduction to what Generics are and how to use them in Chrome. The article only covered generic classes, but the concept of generics can also be applied to other language constructs, including methods, delegates and interfaces. Future articles and the Chrome Language Guide will provide you with more details on that.
Chrome是RemObjects推出的下一代Object Pascal语言,运行于.NET和Mono平台之上。在保留Object Pascal特性的同时,Chrome还借鉴了C#、Java、Eiffel等语言的优秀元素。
一种新的的语言诞生了:
C++ & Java -> C#
Object Pascal -> Chrome
namespace Chrome.Samples.SampleClasses;
interface
uses
System,
System.Collections;
type
SexEnum = (sxMale, sxFemale);
Person = class
private
fNickName: string;
protected
method SetNickName(Value : string);
public
constructor (aName : string; anAge : integer; aSex : SexEnum); virtual;
method GenerateSSN : string;
{ Notice how Chrome doesn't require the declaration of private fields to
store property values, thus making the code more compact and easier to read }
property Name : string;
property Age : integer;
property Sex : SexEnum;
property NickName : string read fNickName write SetNickName;
property Address : PersonAddress := new PersonAddress();
public invariants
{ These conditions are guaranteed to always be respected at the end of
the execution of a non-private method of the Person class }
Age >= 0;
Name <> '';
end;
PersonClass = class of Person;
PersonAddress = class
public
property City : string;
property Street : string;
property Zip : string;
end;
Employee = class(Person)
private
protected
method GetCurrency : double;
public
property Salary : double read GetCurrency;
end;
PersonCollection = class(CollectionBase)
private
protected
{ Type checking events }
procedure OnInsert(Index : integer; Value : Object); override;
procedure OnValidate(Value : Object); override;
{ Other methods }
function GetPersonsByIndex(anIndex : integer) : Person;
function GetPersonsByName(aName : string) : Person;
public
constructor;
method Add(aName : string; anAge : integer; aSex : SexEnum) : Person;
method Add(aPerson : Person) : integer;
method IndexOf(aPerson : Person) : integer;
procedure Remove(aPerson : Person);
{ Notice the use of overloaded array properties }
property Persons[anIndex : integer] : Person read GetPersonsByIndex; default;
property Persons[aName : string] : Person read GetPersonsByName; default;
end;
{ ECustomException }
ECustomException = class(Exception);
implementation
{ Person }
constructor Person(aName : string; anAge : integer; aSex : SexEnum);
begin
inherited Create;
Name := aName;
Age := anAge;
Sex := aSex;
end;
method Person.GenerateSSN: string;
var
lHashValue: integer;
begin
{ Generates a complex string with all the information we have and outputs a fake SSN
using the String.Format method }
lHashValue := (Age.ToString+'S'+Sex.ToString[2]+Name.GetHashCode.ToString).GetHashCode;
if lHashValue < 0 then lHashValue := -lHashValue;
result := String.Format('{0:000-00-0000}', lHashValue);
end;
method Person.SetNickName(Value : string);
require
Value <> '';
begin
fNickName := Value;
ensure
fNickName <> '';
end;
{ PersonCollection }
constructor PersonCollection;
begin
inherited;
end;
method PersonCollection.Add(aName : string; anAge : integer; aSex : SexEnum) : Person;
begin
result := new Person(aName, anAge, aSex);
List.Add(result);
end;
method PersonCollection.Add(aPerson : Person) : integer;
begin
result := List.Add(aPerson);
end;
method PersonCollection.IndexOf(aPerson : Person) : integer;
begin
result := List.IndexOf(aPerson);
end;
procedure PersonCollection.Remove(aPerson : Person);
begin
List.Remove(aPerson);
end;
function PersonCollection.GetPersonsByIndex(anIndex : integer) : Person;
begin
result := List[anIndex] as Person;
end;
function PersonCollection.GetPersonsByName(aName : string) : Person;
begin
for each somebody : Person in List do
if (String.Compare(aName, somebody.Name, TRUE)=0)
then Exit(somebody);
end;
procedure PersonCollection.OnInsert(Index : integer; Value : Object);
begin
OnValidate(Value);
end;
procedure PersonCollection.OnValidate(Value : Object);
begin
{ Notice the use of the "is not" syntax }
if (Value is not Person)
then raise new Exception('Not a Person');
end;
{ Employee }
method Employee.GetCurrency : double;
begin
case Age of
0..15 : Exit(0);
16..24 : Exit(15000);
25..45 : Exit(55000);
else exit(75000);
end;
end;
end.
CH03 - Generics in Chrome (Last updated 4/14/2005)
As part of our support for the upcoming Microsoft .NET Framework 2.0 (code-named Whidbey), Chrome provides full support for using and implementing the so called Generic Types.
What are Generics?
Generic types allow you to write strongly typed code that can later be used on a variety of different types. The classical example for efficient use of generics is container classes. Traditional class libraries (including the .NET Framework 1.1) usually implement containers by using a lowest common denominator class - for example System.Object. All the containers accept objects and will return objects and if you need lists of specific types (say, a custom class Foo), you will end up casting the elements obtained from your list, at runtime, as in this (contrived) example:
method Test;
var
lList: ArrayList := new ArrayList;
begin
lList.Add(new Foo);
lList.Add(5); // oops
//...
(lList[1] as Foo).Bar;
end;
Note how when accessing the element in the list, you need to cast the result back to a Foo manually before you can call its method. If for some reason a wrong type was added to the list (like the "5" that's added in the second line of code above). Your code will break with an invalid cast exception at runtime.
Instead, Generics allow you to write collections that are strongly typed to any given type. If ArrayList were a generic class, the code would look as follows and prevent any chance of type conflicts:
method GenericTest;
var
lList: ArrayList<Foo> := new ArrayList<Foo>;
begin
lList.Add(new Foo);
//lList.Add(5); // this will not compile
//...
lList[1].Bar; // no cast necessary, List[] is strongly typed
end;
The above example already shows you how to make use of existing generic classes from your Chrome code by using the new <> syntax to specify the concrete types (in this case Foo) with which you want to instantiate your generic class. The 2.0 Framework provides a wealth of reusable generic collections in the System.Collections.Generic namespace.
However, in addition to simply consuming existing classes, you can also use Chrome to implement your own generic classes, thus enabling users of your library to use them in a type-safe manner.
Implementing Generic Classes
As an example, let's implement a simple linked list as a generic class and look at the individual language features that are involved in making this work.
type
List<T> = public class
public
constructor(aData: T);
constructor(aData: T; aNext: List<T>
property Next: List<T>;
property Data: T;
method ToString: string; override;
end;
The above class declaration defines a typical node for a linked list; the node contains two properties (the data for the actual node and a link to the next node in the list). It also contains a couple of helper functions which we will implement later to perform common actions on the list.
Generic Parameters
The first thing that will strike you immediately as different from a "normal" class declaration is the use of "T" in several places, for example as the type for the Data property. What is this T? How is it related to the actual types we want to store in our list?
Simply put, T is a so called Generic Parameter of your list class and acts as a placeholder for the actual concrete types with which your list will later be instantiated. If your user eventually instantiates a List<Int32>, then all occurrences of T will represent an integer value; if he instantiates a List<Foo>, they will represent Foos.
Generic Parameters are defined in the first line of your class declaration (List<T> = public class) and can then be used thoughout your class declaration and implementation in any place where a type is expected - as method parameters or results, as property of field types, or in code that deals with types.
Even though this example only declares one generic parameter (T), you can write classes that declare as many parameters as needed and you can use any valid identifier as parameter names (although a common convention is to use uppercase letters starting with "T".
type
Dictionary<Key, Value> = public class ... end;
is a perfectly valid generic class.
Let's implement the ToString method and see how we can work with the Data and Next properties, just as if they were real concrete types:
method List<T>.ToString: string;
begin
result := Data.ToString;
if assigned(Next) then
result := result +';'+Next.ToString;
end;
Note how each node will simply call the ToString method on Data and then defer to the next node's ToString to build the rest of the result; eventually building a complete list of all values by passing over each and every item of the list.
Calling ToString is possible because it is a member of Object and thus available for any possible type T. But what if we need to call more advanced members on the generic elements in our List? That's where Generic Constraints come into play.
Generic Constraints
Chrome allows you to limit the types that will be eligible for a given generic parameter. Limiting the types for T allows your code to make certain assumptions about T and, as a result, perform certain actions on the types that would not be valid on "any" type.
Two types of constraints are available for the 20 framework, Constructor Constraints and Type Constraints.
The first constraint allows you to require the concrete types used to instantiate your generic class to provide a constructor that is without any parameters. This is necessary if, for whatever reasons, you need to call the constructor and create new instances of T in your class. Take for example the following addition to our List<T> class:
method List<T>.AddNew: T;
var
lEnd: List<T>;
begin
lEnd := self;
while assigned(lEnd.Next) do
lEnd := lEnd.Next;
lEnd.Next := new List<T>;
lEnd.Next.Data := new T;
end;
Note how the last line of code creates a new instance of T and stores it at the end of the list. This is only possible if we can rely on T having a simple constructor without parameters, so we need to specify the Constructor Constraint for our generic class, using the "T has constructor" statement in the generic's where clause:
type
List<T> = public class
where T has constructor;
public
//...
end;
Secondly, lets assume that you want to add a Sort method to your list, which will use your favorite sort algorithm (say, Random Sort) to print the elements in your list into order. This presents us with a similar problem as before: to sort the list, we must be able to compare the individual items, but how can you compare the items without knowing their type? The answer is simple: let the individual items provide their own logic for comparing themselves via the IComparable interface and set a Type Constraint on your class to require that any concrete type used to instantiate your generic class must implement this interface.
This is done by supplying the "T is IComparable" statement in the where clause:
type
List<T> = public class
where T has constructor, T is IComparable;
public
//...
end;
The where clause
The where clause for a generic type can list any number of constraints, separated by commas. You can require several interfaces to be implemented by any given generic parameter, or you can require a specific ancestor class by specifying the said class. You can also mix requirements for several parameters, e.g.:
type
Dictionary<Key, Value> = public class ...
where Key is IComparable, Key has constructor, Value is Component;
// ...
end;
which defines a generic Dictionary class where the Key type must be comparable (understandably) & able to be created and the Value type must be Component or a descendant.
Summary
We hope this article has given you a quick introduction to what Generics are and how to use them in Chrome. The article only covered generic classes, but the concept of generics can also be applied to other language constructs, including methods, delegates and interfaces. Future articles and the Chrome Language Guide will provide you with more details on that.