Posts tagged delphi_12_specific

TZDB 1.2, now with Delphi 2009 and 2010 support

TZDB 1.2 was released today. You can grab it here, and view the latest changes here.



The most notable changes are:

  • Latest tz database (2010m). Also removed a few less then useless time zones from the official distribution.
  • Bug fixes, and a new testing module that actually highlighted those bugs in the first place.
  • Support for Delphi 2009 and 2010. If compiled for these versions, TZDB provides some features of Delphi XE’s TTimeZone class (for API compatibility).

TypeInfo workaround

This is going to be a short one. Just wanted to share a simple and elegant work-around for this QC issue:

type
  TypeOf<T> = record
    class function TypeInfo: PTypeInfo; static;
    class function Name: string; static;
    class function Kind: TTypeKind; static;
  end;

{ TypeOf<T> }

class function TypeOf<T>.Kind: TTypeKind;
var
  LTypeInfo: PTypeInfo;
begin
  LTypeInfo := TypeInfo;

  if LTypeInfo <> nil then
    Result := LTypeInfo^.Kind
  else
    Result := tkUnknown;
end;

class function TypeOf<T>.Name: string;
var
  LTypeInfo: PTypeInfo;
begin
  LTypeInfo := TypeInfo;

  if LTypeInfo <> nil then
    Result := GetTypeName(LTypeInfo)
  else
    Result := '';
end;

class function TypeOf<T>.TypeInfo: PTypeInfo;
begin
  Result := System.TypeInfo(T);
end;

Now, you can obtain the type information for any type by simply using TypeOf<T>.TypeInfo:

type
  TRecord<T> = record
    FVal: T;
  end;

  TIntRecord = TRecord<Integer>;

begin
  // TypeInfo(TIntRecord); // Fails with compile-time error;
  WriteLn(GetTypeName(TypeOf<TIntRecord>.TypeInfo));

  ReadLn;
end.

You can also use this construct to obtain the type information for generic types from within themselves:

type
  TSomeRec<T> = record
    function GetMyName: String;
  end;

function TSomeRec<T>.GetMyName(): String;
begin
  // Result := GetTypeName(TypeInfo(TSomeRec<T>)); // Fails
  Result := TypeOf<TSomeRec<T>>.Name;
end;

The only thing to notice is that compile time [DCC Error] E2134 Type ‘XXX’ has no type info errors will not be triggered for types having no type info. Instead, the TypeOf<T>.TypeInfo method will return nil.

DeHL 0.6 available

DeHL

Yes I know I have skipped 0.5. The reality is that 0.5 was due a long time ago, but I did not have enough free time on my hands to complete the unit testing for all new features added. I always try to add as many unit tests as possible to test all possible scenarios.

This release features a general reorganization of some key concepts, new type extension mechanics, boxing, a lot more collections, better support for associative collections through specific Enex extensions … and many more. I am not going to lay out all the changes here since there are quite a few of them. You can view the change log here.

One important note, check out the DeHL.Base.pas unit. There are a number of TOGGLE_XXXXX constants. Toggle them to true or false (if you are using Weaver FT builds) to enable or disable specific functionality. In Tiburon (Delphi 2009) all the features are disabled by default (to be on the safe side).

Get the latest build here. If you stumble upon some bugs please report them here.

Have Fun!

DeHL documentation

Since I had a few requests, I have started putting together some documentation on DeHL. It’s pretty basic and only includes some conceptual stuff, not API documentation. You can find it here.

Version 0.5 is shaping up quite well with tons of changes, and new stuff coming in — but that’s a another story.

Not much in this post,  Cheers!

What’s new in DeHL 0.4

In the previous post I have mostly talked about Enex (Enumerable Extensions) I have enabled for all DeHL’s collection classes. Now I will list the other changes that got into this release that may be of interest to you:

  • Enex (Enumerable Extensions) in DeHL.Collections.Enex. Support for chained queries, predicates and selectors.
  • THashTree (A tree that uses hashes to access the child nodes). in DeHL.Collections.HashTree.
  • TConverter can be created with given IType‘s as opposed from the default ones.
  • TSuppressedWrapperType which can be used to wrap another IType. It’s main function is to forward all calls to the enclosed IType except the Cleanup() and Management() ones. This way you can use temporary collections inside your collection without the “cleanup” problem.
  • Fixed 2 bugs in Resize() method of THashSet and TDictionary which resulted in corrupted heap in some cases.
  • TLinkedList properties called First and Last were renamed to FirstNode and LastNode to avoid naming conflicts with Enex’s First() and Last() methods.
  • Fixed a bug in TQueue‘s enumerator object. It enumerated the queue’s elements quite wrong.
  • Renamed TScopedPtr to a better name: Scoped.
  • Added a new type: Nullable. It can be used to represent a NULL value for non-object types such as Integer.
  • New life-time extensions for TRefCountedObject. Each ref-counted object can now “keep-alive” other ref-counted objects. This was an useful addition in order to support query-chains in Enex.

How to use the new Nullable<> type:

function GetTheSum(const AList: TList<Integer>): 
    Nullable<Integer>;
begin
  { Check if the list is non-null and has elements }
  if (AList = nil) or (AList.Count = 0) then
    Result.MakeNull();
  else
    Result := AList.Sum();
end;

var
  LSum: Nullable<Integer>;
begin
  { ... }
  LSum := GetTheSum(...);
  if not LSum.IsNull then
    WriteLn(LSum.Value);
end;

I agree, this is not the best example, but it is OK to get you started. Nullable values are useful when you function may return or even accept a normal (let’s say Integer) value or a NULL.

DeHL 0.4: Linq-ifying Delphi

Since I have started working on DeHL, one of the main purposes was to get to the point where I could use Linq-like extensions on collection classes. Of course I could not call the new functionality Linq since it’s not language-integrated, so I decided to give it another name: Enex (aka Enumerable Extensions). All collection classes in DeHL now support a wide set of methods that allow querying and selecting elements from the starting collection. A lot of work has been put into this release, ranging from extensions to type system, object life-time management, optimizations and bug-fixes and of course a lot of testing.

Now, back to Enex, let’s visit some cool examples of what the new functionality can do:

Let’s check a very simple example. Assuming you have a list of integers and you want to find the sum of all elements that are greater than the list average. In the “times before” you wold write something like this:

  { Calculate the average of the list }
  Sum := 0;

  for I := 0 to List.Count - 1 do
    Sum := Sum + List[I];

  Avg := Sum div List.Count;

  { Calculate the sum of the elements above average }
  Sum := 0;

  for I := 0 to List.Count - 1 do
    if List[I] > Avg then
      Sum := Sum + List[I];

… Now it is as simple as this:

  { ... }
  Sum := List.WhereGreater(List.Average()).Sum();
  { ... }

Now, let’s see another more complex example. Considering that you have two collections of integers. You have to determine the maximum value common to both collections. Check it out:

  { ... }
  Max := AColl1.Intersect(AColl2).Max();
  { ... }

Next: what is the sum of all odd numbers in interval [0..100]?

  Sum := TEnexEnumerable<Integer>
    .Interval(0, 100)
    .Where(function(Arg1: Integer): Boolean
      begin
        Exit(Odd(Arg1);
      end)
    .Sum();

The last one: You want to take all integers from a list with their absolute value and only once; and you want to write them on the screen directly:

  for I in ARandomList
    .Select(function(Arg1: Integer): Integer
      begin
        Exit(Abs(Arg1));
      end)
    .Distinct()
  do
    WriteLn(I);

What’s working and what’s not:

  • Some problems in the compiler prevent the use of generic function Select<TOut> and Cast<TOut> for now. These two could have been used to transform each element of the collection to some other type.
  • Everything is based in interfaces to make use of automatical garbage collection. the only restriction is that the first enumerable (from which the chain starts) should be either an interface or must be destroyed explicitly.
  • All collections have overriden versions of some functions. For example in a list, the First() method is implemeted directly so that you benefit from full speed whenever possible.
  • No lambdas … that makes writing selectors or predicates rather unpleasant.
  • Sum() and Average() are only supported for types registered as integers or reals (including BigCardinal and BigInteger).
  • All selector and predicate-based selection is done on-the fly so there is no additional memory used!

There are of course much more possible use cases for Enex-enabled collections, I simply do not have the time to list all of them.

You can download the latest version of DeHL here.

More about enumerables

In the last post I have described how enumeration works in Delphi. Now I will try to expand the subject a bit and make a more general description of what enumerability actually means and how it can solve some basic problems and patterns.

An enumrebale is not necessarily a collection“. You should keep in mind that enumerability doesn’t apply only to collections. It is a more abstract concept. Enumeration can apply to any kind of sequence — abstract, mathematical or a collection. As an example let’s talk about streams — streams are also enumerables, in some cases these behave exactly like enumerable collections: For streams that do not have a defined size (like for example downloading a file over a HTTP stream that doesn’t say the file size). All you can do in these cases is to read bytes from the stream until you read the bottom and receive an EOF notification.

Abstract vs concrete sequences“. An abstract sequence in my definition is one that doesn’t occupy space and each new element is generated on the fly by the enumerable. A concrete sequence is more akin to a collection which already has its elements stored somewhere and all it does is to fetch them when enumerating.

In the next example I took a known mathematical sequence which I am sure all of you are acquainted with: the Fibonacci sequence; and created an abstract enumerable which at each iteration calculates the next number:

type
  TFibonacciEnumerator = record
  private
    FCurrent, FPrev,
      FCount, FMaxCount: Cardinal;

    function GetCurrent: Cardinal;
  public
    { Move to the next calculation }
    function MoveNext(): Boolean;

    { Reads the current number }
    property Current: Cardinal read GetCurrent;
  end;

  Fibonacci = record
  private
    FLimit: Cardinal;

  public
    { Returns the enumerator object }
    function GetEnumerator(): TFibonacciEnumerator;

    { Static function that }
    class function OfLength(const ALength: Cardinal): Fibonacci; static;
  end;

{ TFibonacciEnumerator }

function TFibonacciEnumerator.GetCurrent: Cardinal;
begin
  Result := FCurrent;
end;

function TFibonacciEnumerator.MoveNext: Boolean;
var
  LTemp: Cardinal;
begin
  { Check if the end of the chain }
  if FCount >= FMaxCount then
    Exit(false);

  Result := true;

  { Make the next calculation }
  if FCount <= 1 then
    FCurrent := FCount
  else
  begin
    LTemp := FCurrent;
    FCurrent := FCurrent + FPrev;
    FPrev := LTemp;
  end;

  Inc(FCount);
end;

{ Fibonacci }

function Fibonacci.GetEnumerator: TFibonacciEnumerator;
begin
  Result.FCurrent := 0;
  Result.FPrev := 0;
  Result.FCount := 0;
  Result.FMaxCount := FLimit;
end;

class function Fibonacci.OfLength(const ALength: Cardinal): Fibonacci;
begin
  if ALength = 0 then
    raise EArgumentOutOfRangeException.Create('ALength should be > 0');

  Result.FLimit := ALength;
end;

var
  Number: Cardinal;
begin
  { Show the first 100 Fibonacci numbers }
  for Number in Fibonacci.OfLength(100) do
    WriteLn(Number);

  ReadLn;
end.

What is cool about this example is the fact that you do not occupy any memory with the calculated numbers, those are made on-the-fly.

This example did not demonstrate another important aspect of enumerables and exactly: “materializing” abstract sequences. If my Fibonacci record was actually a class derived from TEnumerable<Cardinal>, I could have written this:

var
  List: TList<Cardinal>;
begin
  List := TList<Cardinal>.Create(Fibonnaci.OfLength(100));
end.

This would have “materialized” the abstract sequence generated by the Fibonacci object and stored each value in a concrete sequence (the List collection).

Unfortunately Delphi’s generics support is at it’s infancy so not many features are yet available in the standard classes to exploit the power of Enumerables. I predict this will change over time and more cool stuff will appear either directly in the RTL or in form of 3rd-party libraries.

Enumerables in Delphi

It is not a surprise that most programming languages in our time have the built-in support for enumerators/iterators. This is a mandatory feature since enumerators and enumerable collections simplify the development of applications and make the code cleaner. Languages such as C#, Java or Delphi have built-in syntax constructs that allow simplified and clean ways of enumerating over a collection by the means of  “foreach” or special “for” loops.

In Delphi, enumerators have been added to the language for some time now in form of a special “for” loop (you can read in the Help about it more). It allows enumerating elements of an array, string and in most collection classes. While in the case of intrinsic types, the compiler simply transforms the loop into an ordinary “for” loop, in the case of collection classes things become more interesting. But let’s take each case separately:

Enumerating over a String:

var
  LString: String;
  LChar: Char;
begin
  LString := 'Hello World';

  for LChar in LString do
    Write(c);
end;

It’s simple as that! It does not have such a negative impact on the speed and make your code look much cleaner, it also helps avoid using an index variable. The same method can be applied for AnsiString, WideString and ShortString.

Enumerating over an Array:

var
  LArr: TBytes;
  LByte: Byte;
begin
  LArr := TBytes.Create(1, 2, 3, 4, 5);

  for LByte in LArr do
    Write(LByte);
end.

Looks simple. And indeed it is! There are more use cases for different types of arrays, but you can find that in the Help.

Enumerating over a collection:

var
  List: TList<Integer>;
  I: Integer;
begin
  List := TList<Integer>.Create();
  List.AddRange([1, 2, 3, 4, 5]);

  for I in List do
    WriteLn(I);
end.

In this case the overhead for the “for” loop is higher since a call to List.GetEnumerator() is made to obtain an enumerator object. Then at each iteration the MoveNext() and Current() methods are called on the enumerator to move to the next element in the list and retrieve its value.

There are in fact only a few rules that you must abide in order to support enumeration in your collections:

  1. You must have either a class, interface or record type.
  2. Your class, interface or record must expose a GetEnumerator() function that returns an enumerator.
  3. The enumerator can be either a class, interface or a record type.
  4. The enumerator must expose a MoveNext() function that returns a Boolean and a Current property that returns the current element.
  5. When the enumerator is created there is no current element selected. Only after the first call to MoveNext() your enumerator must select the first element in the collection.
  6. MoveNext() must return true if the next element was selected or false if the collection is finished.

Extreme case — enumerating over a record using a record enumerator:

type
  { The enumerator record }
  TRecordEnumerator = record
  private
    FArray: TBytes;
    FIndex: Integer;

    function GetCurrent: Byte;
  public
    function MoveNext(): Boolean;
    property Current: Byte read GetCurrent;
  end;

  { The record/collection that will be enumerated }
  TRecordCollection = record
  private
    FArray: TBytes;
  public
    function GetEnumerator(): TRecordEnumerator;
  end;

{ TRecordCollection }

function TRecordCollection.GetEnumerator: TRecordEnumerator;
begin
  Result.FArray := FArray;
  Result.FIndex := -1;
end;

{ TRecordEnumerator }

function TRecordEnumerator.GetCurrent: Byte;
begin
  Result := FArray[FIndex];
end;

function TRecordEnumerator.MoveNext: Boolean;
begin
  Inc(FIndex);

  if FIndex >= Length(FArray) then
    Exit(false);

  Exit(true);
end;

var
  LColl: TRecordCollection;
  B: Byte;

begin
  LColl.FArray := TBytes.Create(1, 2, 3, 4, 5, 6);

  for B in LColl do
    WriteLn(B);

  ReadLn;
end.

The compiler doesn’t really care what it is enumerating and what it uses to do that. It simply must find the required methods exposed in the enumerated collection and in the enumerator.

DeHL 0.3 is out

I’m proud to announce the 0.3 version of DeHL library. This release focused on bug-fixes and extensions to type support engine. This version should pretty much stabilize what I started and should leave me more time to extend other interesting parts of the library such as collections.

For those who are new, DeHL is a project of mine designed to fill in some holes in the Delphi’s RTL — most notably, some centralized and OOP-ish Date/Time support, more generic collection classes and “big integer” math support. DeHL also builds up a new layer of “type support” which is very useful to abstract all type management tasks in generic classes. This projects only supports Delphi 2009 (and above) since Generics, Anonymous methods, and Unicode are widely used whithin the library. So even if you are not interested in the library itself, it may prove a good read if you are interested in all new features Delphi 2009 has to offer.

New features:

  • Type support classes now expose methods to convert a type from an to a Variant.
  • Based on the newly added Variant conversion, a new type: TConverter<T1, T2> is present. You can use it to support “blind” conversion in a class.
  • Much improved type support system
    • Now more functionality is split between generic and non-generic variants of the type support classes (IType, IType<T>, TType, TType<T>).
    • TypeSupport has been renamed to TType and IType repectively.
    • IType/TType now export Name, Size, Management, TypeInfo and Family properties. You can use those properties to get more information about your generic type.
    • Custom type registration has been improved. A new cleaner API can be used to register you custom types into the DeHL’s type system.
    • TType<T>.Default is now an overloaded function. The first form is the usual one. The second form receives a set of “type families” that are expected to represent the generic type. This way you can effectively impose run-time type restrictions.
    • TClassType is now generic with T being class-restricted. This allows to avoid compile-time type incompatibilities.
  • All important standard types in Delphi are now supported. This support required the working custom types system, since these types can’t be handled “by default“:
    • Boolean, ByteBool, WordBool, LongBool
    • TDateTime, TTime, TDate
    • NativeInt, NativeUInt
    • Real
    • OleVariant
    • UCS4Char, UCS4String
    • UTF8String
    • RawByteString

Bug fixes:

  • Fixed a bug in BigCardinal and BigInteger variant types related to string conversion.
  • A few changes in TDictionary to avoid compiler problems.
  • In a multi-threaded environment, the custom type support would not unlock properly leading to a dead-lock.
  • Cleanup routines for TDictionary and THashSet were flawed since those used FillChar to clear the array. Managed types had problems with that.
  • Char type support class now acts properly.

Note: It may be possible that the Testing project will not compile due to a bug in the Delphi compiler. But that should not prevent the usage of the library itself.

The download section can be found here.

“in” is dead, long live “CharInSet” (or maybe not)

I’ve this question come up a few times, so …

Since Delphi 2009 you have probably noticed this warning: “[DCC Warning] Unit1.pas(27): W1050 WideChar reduced to byte char in set expressions.  Consider using ‘CharInSet’ function in ‘SysUtils’ unit.” which suggests you to use the CharInSet function instead. CharInSet still requires a simple set of AnsiChars and fails (returns false) is the code of the passed character is greater than 255 which makes it useless for Unicode characters.

Putting it simple: replacing the “a in b” statement with CharInSet(a, b) will simply silence the compiler. Nothing more.

You should really consider using Character unit if you want to check whether a character is a letter, digit or is a part of another “standard set of chars”.

Take this example:

uses Character;
begin
  if C in ['A'..'Z'] then // <-- OLD WAY
    WriteLn('The character is an upper-cased letter.');

  if IsUpper(C) then // <-- UNICODE WAY
    WriteLn('The character is an upper-cased letter.');
end;