Posts tagged DeHL

Delphi anonymous methods are …

… too fat.

Surely writing code like this gets frustrating very fast. I want my lambdas!!

  { Lovely, isn't it? }
  LGroupJoined := LOwners.Op.GroupJoin<TPet, TPerson, TOwnedPets>(LPets,
    function(Arg1: TPerson): TPerson begin Result := Arg1; end,
    function(Arg1: TPet): TPerson begin Result := Arg1.FOwner; end,
    function(Arg1: TPerson; Arg2: ISequence<TPet>): TOwnedPets
    begin
      Result.FOwnerName := Format('%s, %s', [Arg1.FLast, Arg1.FFirst]);
      Result.FPetNames := Arg2.Op.Select<String>(
        function(Arg1: TPet): String begin Result := Arg1.FName; end);
    end
  );

  for LOwnedPets in LGroupJoined do
  begin
    WriteLn('Person ', LOwnedPets.FOwnerName, ' owns: ');
    Write('    ');

    for LName in LOwnedPets.FPetNames do
      Write(LName, ', ');

    WriteLn;
  end;

In other news, there is a working version of Collections 2.0 in the SVN. Most of the changes were sitting on my hard drive for a year now. I just made sure that the last touches are applied and that this version compiles in Delphi XE2. I have also applied a few fixes that enabled the package to work in 64 bit environment.

 

Be advised: The unit tests are completely broken. I have no short term plans on making them work. The amount of time needed for that is staggering. I am currently focused on features so I won’t touch them in a while.

 

P.S. I have to say that I am very disappointed with the quality of the IDE when generics are involved. I had countless unpleasant problems in the past but was expecting things to get better in XE2. Not so. My IDE froze countless time when trying to edit some source files. Whenever I would type a “.” or a “(” it would be gone for good. This makes me wonder whether Collections will be usable at all to you guys. With such buggy support I can predict only crashes. And don’t get me started on the EXE size …

TZDB 1.7.110207 and Collections 1.1.1

TZDB 1.7.110207

No code changes, just an updated DB from “The tz Database“. There are a small number of changes there so upgrade only if you want to stay 100% up to date. I would just recommend you skip it. As usual, you can get the latest version here.

Collections 1.1.1

This is a bug fix release that addresses the following issues:

  1. No possibility to remove an value from a list or associative collection without invoking the “removal procedures”. This results in freed objects for object based collections. In pre-1.1 versions this wasn’t necessarily an issue. (Thanks Mason for the report, hope this unblocks your scripting project)
  2. Removal procedures were not invoked for bidirectional dictionaries.
  3. TObjectMultiMap<TKey, TValue> still used a “class” restriction on TValue type parameter.
  4. Fixed Delphi 2010 build. Note that by “fix” I mean — disabled Collections.Dynamic code and TBitSet class. Cannot make the compiler accept those (circular unit problems that were fixed in XE).

Get the bug fix release here.

Collections 1.0.1

I’ve just put the 1.0.1 bug fix version up for download. It fixes XE compatibility issues, documentation fixes (thanks to Denisa Ilascu for her hard work). I don’t have access to Delphi 2010 and can’t say for sure whether it will work (I hope so).

EDIT: Confirmed, it works on Delphi 2010.

Download link here.

Collections 1.0.1 soon

I will get an 1.0.1 update soon addressing XE compatibility issues some people are experiencing and some documentation fixes. Stay tuned.

Collections 1.0

Last week I have finally got some free time on my hands to do something I was planning for a long time now: create a new package that will only provide a number of collections. What this means is that I took DeHL and stripped out everything that was not collection related; remodeled the classes to rely on Generics.Collections and Generics.Defaults standard units; and finished all the documentation.

What remained is a pure collection orientated package called (very unimaginatively)  Collections.  If you ever used DeHL’s collections you will most certainly recognize the code. But, while stripping out the bloat I was forced to re move some things that relied on that bloat:

  • No more reliance on IType/TType. Collections now use IComparer and IEqualityComparer provided by Generics.Defaults.
  • All associative collections use TPair now.
  • Because IType was designed to provide cleanup for the enclosed values in the collections some reworking was necessary to make collections more Delphi-like. You still get the OwnsObjects, OwnsKeys and OwnsValues properties on the object variants.
  • No more Op.Cast<> support. This Enex operation relied on TConverter which relied on IType and so on and so forth. Instead use a Op.Select with a casting predicate.
  • Using NativeInt instead of NativeUInt in some places. This was a menace for a lot of people.

Note that Collections is not base-class or interface compatible with Generics.Collections. I could not make the proper compatibility because of interface support.
So what would you expect in the package:

  • Full inline documentation. All APIs are properly documented. If you find any typos or errors please fill a bug!
  • Test cases for mostly everything provided.
  • A wide range of collections (much more than just stack, queue, list and dictionary).
  • Enumerable extensions.
  • Easy extensibility.
  • Based on NativeInt instead of Integer. Ready at interface level for 64bit support.

Warning: This is a 1.0 release. There may still be some problems. If you find any please fill in a bug report. If you feel that you can help and implement something that is not included in the package feel free to contact me!

DeHL 0.8.3

DeHL

I will be brief as usual — version 0.8.3 of DeHL is out. The downloads can be found on this page and changelog on this page. This release “fixes” some of the things I wanted fixed for a long time, so it seemed this is the perfect moment for this to happen. A new unit is introduced — DeHL.Tuples — which brings seven generic Tuple<…> types. I have also finished moving away from Integer and Cardinal to NativeInt and NativeUInt through all DeHL.

Breaking changes are:

  1. DeHL.Converter was renamed to DeHL.Conversion. This name change was done mostly to reflect the nature of the unit. It does not contain one simple class anymore. Now there is a fully featured conversion system.
  2. TBinaryTree<T> (in DeHL.Collections.BinaryTree) is no more. It was written way in the beginnings of the project and was buggy, incomplete and utterly useless.
  3. And the most visible and breaking of all changes is the removal of TKeyValuePair<TKey, TValue> (in DeHL.KeyValuePair). It was replaced with KVPair<TKey, TValue> (in DeHL.Tuples). The easiet way to get over this change is to find and replace all TKeyValuePair instances with KVPair and all DeHL.KeyValuePair uses with DeHL.Tuples.

Now, obviously an example using Tuples:

uses
  SysUtils, DeHL.Tuples;

{ If you are lazy and do not wish to declare a new record type
  to be used as result. Use Tuple<..> do to that. }
function GiveMeSomeData(): Tuple<String, Integer, Integer>;
begin
  { ... Do some processing ... }
  Result := Tuple.Create('Some data', 100, -99);
end;

var
  LResult: Tuple<String, Integer, Integer>;
begin
  { Obtain the result }
  LResult := GiveMeSomeData();

  { And write the results to the console }
  with LResult do
    WriteLn('Something was done with result: ', Value1, ', ', 
      Value2, ', ', Value3, '!');
end.

The new conversion engine handles most of the possible conversions and also allows registering custom ones:

uses
  SysUtils,
  DeHL.Conversion,
  DeHL.Collections.List;

type
  { Declare some type that cannot be converted into integer directly.
    We're making up an "int". }
  TRecordInt = packed record
    FValueAsString: string;
    FSign: Boolean;

    constructor Create(const AInt: Integer);
  end;

{ TRecordInt }

constructor TRecordInt.Create(const AInt: Integer);
begin
  { Decompose an int }
  FValueAsString := IntToStr(Abs(AInt));
  FSign := (AInt < 0);
end;

var
  LInputList: TList<TRecordInt>;
  S: String;
  I: Integer;
begin
  { Create a list of TRecordInt }
  LInputList := TList<TRecordInt>.Create();

  { Fill it with some random values (positive and negative) }
  for I := 0 to 10 do
    LInputList.Add(TRecordInt.Create(Random(MaxInt) - (MaxInt div 2)));

  { Now comes the interesting part ... register a custom converter
    from TRecordInt to Integer }
  TConverter<TRecordInt, Integer>.Method :=
    function(const AIn: TRecordInt; out AOut: Integer): Boolean
    begin
      { Convert the TRecordInt back to an integer }
      Result := TryStrToInt(AIn.FValueAsString, AOut);
      if Result and AIn.FSign then
        AOut := -AOut;
    end;

  { Now print the values to the console. Convert them from TRecordInt to
    Integer then to String }
  for S in LInputList.Op.Cast<Integer>.Op.Cast<String> do
    WriteLn(S);
end.

TConverter is also smart enough to figure out that “type MyInt = type Integer” is actually equivalent to Integer. If there is no explicit custom conversion method registered for it the converter for the standard type will be selected is possible. In the worst case, when TConverter cannot convert directly between the given types, it falls back to Variant conversion (using TType.TryConvertToVariant and TType.TryConvertFromVariant) which all types registered with DeHL’s type system, if possible, should implement.

Well, that’s all for today,
Have Fun!

DeHL 0.8.2 is out

DeHL

I’ve just released the version 0.8.2 of DeHL. The downloads can be found on this page and changelog on this page.
Again, this is a minor release with a few bugs fixed and a new feature: TString (as asked in this comment).
As you might have guessed already, TString is a wrapper record modeled on .NET’s System.String class. Unfortunately I was unable to use most of the RTL’s string functionality so the “wrapper” grew quite a bit from my original expectations.

But … enough talk, here are some usage scenarios that someone may find useful:

var
  LStr: TString;
begin
  { Overloaded operators and a special function "U" }
  LStr := U('Hello World for the ') + 10 + 'th time!';

  { Do some random operations }
  if (LStr.ToUpper().Contains('HELLO')) and
     (LStr.Contains('HeLLo', scLocaleIgnoreCase)) then
  WriteLn(LStr.ToString);

  { Now let's select all the distinct chars from the string }
  WriteLn(
    LStr.Concat(LStr.AsCollection.Distinct.Op.Cast<string>).ToString
  );
end.

TString overloads all sane operators: Equality, Inequality, Implicit conversions, Addition, Subtraction and offers functions to convert to and from UTF8 and UCS4 (via RTL of course). I also need to iron a few things about about Enex integration for the next minor release.

The other small improvement that I added relates to the collection package. All simple collections (not the Key/Value pair ones) implement a sort of “where T is the_class, select it as such” operation. Check out this example:

var
  LList: TList<TObject>;
  LBuilder: TStringBuilder;
  LObject: TObject;
begin
  LList := TList<TObject>.Create;

  { Populate the list with some random objects }
  LList.Add(TInterfacedObject.Create);
  LList.Add(TStringBuilder.Create);
  LList.Add(TStringBuilder.Create);
  LList.Add(TObject.Create);

  { Now select the objects we're interested in (string builders) }
  for LBuilder in LList.Op.Select<TStringBuilder> do
    WriteLn(LBuilder.ClassName); // Do stuff

  { Or select everything (not actually required - an example) }
  for LObject in LList.Op.Select<TObject> do
    WriteLn(LObject.ClassName); // Do stuff
end.

If it’s still not clear what this operations does, let me explain. It basically consists of two operations: Where and Select. First, each object is checked to be of a given class and then this object is cast to that class so you can iterate directly using a FOR .. IN loop only over the objects you want to. Of course doing that for TObject makes no sense (as in example) … but well … that was an example.

Well, that’s all for today,
Have Fun!

DeHL 0.8.1

DeHL

I’ve just released the version 0.8.1 of DeHL. The downloads can be found on this page and changelog on this page.

This is mostly a fix release with only one major feature – Cloning (in DeHL.Cloning). The rest of the changes are either bug fixes or janitorial changes.

Have Fun!

Replication

I must say, I am still pretty exited by the extended RTTI in Delphi 2010. It makes life so much easier in many circumstances. “Cloning” (I call it replication) is one of those. Say hello to TReplicator<T> (in DeHL.Replication). It can take any type and create an exact copy (including deep cloning for complex types).

The following example should pretty much explain what and how:

uses
  SysUtils,
  DeHL.Replication,
  DeHL.Collections.List;

type
  { Define a test class }
  TData = class
    { Pointer to self }
    FSelf: TObject;

    { A chunck of bytes }
    FSomeData: TBytes;

    { Some dummy string }
    FName: string;

    { An internal pointer. Marked as non-replicable -- will not be copied }
    [NonReplicable]
    FInternalPtr: Pointer;

    { This field will be copied by reference. It means that the new
      object will have the reference to the same TList<String> and not
      a copy of it. }
    [ByReference]
    FList: TList<String>;
  end;

var
  LInput, LOutput: TData;
  LReplicator: TReplicator<TData>;
begin
  { Create the input }
  LInput := TData.Create;
  LInput.FSelf := LInput;
  LInput.FSomeData := TBytes.Create(1, 2, 3, 4, 5, 6);
  LInput.FName := 'Some Name';
  LInput.FInternalPtr := Ptr($DEADBABE);
  LInput.FList := TList<String>.Create(['Hello', 'World']);

  { Create the replicator }
  LReplicator := TReplicator<TData>.Create;

  { Create a copy of LInput }
  LReplicator.Replicate(LInput, LOutput);

  { ... }
end.

As you can see we take LInput and create a copy into LOutput. Also note the use of [NonReplicable] and [ByReference] attributes on two fields of TData class. NonReplicable forces the replication process to skip the field and ByReference forces a shallow copy of reference fields (objects, pointers to arrays and dynamic arrays).

For the ease of use, a TCloneableObject is defined in the same unit (DeHL.Replication). TCloneableObject provides a method called Clone() which returns a copy of the object:

uses
  SysUtils,
  DeHL.Replication;

type
  { Define a test class }
  TTestMe = class(TCloneableObject)
    { ... }
  end;

var
  LInput, LOutput: TTestMe;
begin
  { ... }
  LOutput := LInput.Clone() as TTestMe;
  { ... }
end.

Note: This is just a preview of a new feature finding it’s way into DeHL. It’s currently only in the SVN repository. As usual, I need to write unit tests and so on to make sure it actually has no bugs (or at leas not that many bugs ;).

Here you go — DeHL 0.8

DeHL

This is going to be a short one. After months of no releases, here it is: DeHL 0.8 (see changelog for the list of changes on this release). As I mentioned previously, this release will only work on Delphi 2010, since the number of changes required to support serialization was quite big.

I must confess, this is the first time in my programming career that I had to interact with serialization (excluding the times when the frameworks do that for you). I had to learn quite a bit and changed my internal design three times.

So, here’s an example how to serialize a TFormatSettings structure into binary format:

var
  LBinFile: TFileStream;
  LBinSerializer: TBinarySerializer<TFormatSettings>;
  LSettings: TFormatSettings;
begin
  { Obtain current thread's format settings }
  GetFormatSettings(GetThreadLocale(), LSettings);

  { Create a serializer and a file stream }
  LBinSerializer := TBinarySerializer<TFormatSettings>.Create();
  LBinFile := TFileStream.Create('dump_obj.bin', fmCreate);

  { Serialize the structure }
  try
    LBinSerializer.Serialize(LSettings, LBinFile);
  finally
    LBinFile.Free;
    LBinSerializer.Free;
  end;
end;

… and here’s an example how to deserialize it:

var
  LBinFile: TFileStream;
  LBinSerializer: TBinarySerializer<TFormatSettings>;
  LSettings: TFormatSettings;
begin
  { Create a serializer and a file stream }
  LBinSerializer := TBinarySerializer<TFormatSettings>.Create();
  LBinFile := TFileStream.Create('dump_obj.bin', fmOpenRead);

  { Deserialize the structure }
  try
    LBinSerializer.Deserialize(LSettings, LBinFile);
  finally
    LBinFile.Free;
    LBinSerializer.Free;
  end;
end;

Note: I have written quite a few unit tests to support the new changes, but most certainly there are hidden bugs. If you find one please report it here.

Other Note: I exhausted my idea jar regarding new features. If you have an idea please do not hesitate to drop me a comment or an email.

Have Fun!