The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My work

  • My badges

  • Twitter Updates

  • My Flickr Stream

    NoRESInForms

    MPS_5159

    MPS_5158

    More Photos
  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,031 other followers

Delphi – for … in on enumerated data types

Posted by jpluimers on 2009/10/27

I like enumerated type a lot.
The allow you to perfectly describe what the members of such a type actually mean, much more readable than a bunch of integer constants!

Given an enumerated type like TTraphicLightColors

type
  TTraphicLightColors = (Red, Orange, Green);

I always wondered why  - since the for ... in statement was added to the structured statements part of the Delphi language – it is not possible to use a for … in statement like the this:

</span>
<pre>var
  TraphicLightColor: TTraphicLightColors;
begin
  try
    for TraphicLightColor in TraphicLightColor do
      ShowValueAsTraphicLightColor(Ord(Value));
    // [DCC Error] EnumerationEnumeratorDemoProcedures.dpr(63): E2430 for-in statement cannot operate on collection type 'TTraphicLightColors'
end;

Somehow, for … in expects a collection type.
A request for the for … in do on enumerated types compiler feature is in QC, but it is closed with reason “Won’t do”.

Back in Delphi 2007, I tried working around this by writing a type implementing the GetEnumerator pattern myself, but got Internal Errors when compiling anything but the most basic sample.

Until today, where I found how I could get that most basic sample to work!
It is an example on how you could implement this: it is research, so you decide if you find the result practical enough to use yourself.

Lets start with the REnumerationEnumerator record and the TEnumerationEnumerator enumerator class that it generates:

unit EnumerationEnumerator;

interface

uses
  TypInfo;

type
  TEnumerationEnumerator = class
  private
    FMinValue: Integer;
    FMaxValue: Integer;
    FValue: Integer;
  public
    constructor Create(aEnumeratorTypeInfo: PTypeInfo);
    function GetCurrent: Integer;
    function MoveNext: Boolean;
    property Current: Integer read GetCurrent;
  end;

  REnumerationEnumerator = record
  private
    EnumeratorTypeInfo: PTypeInfo;
  public
    constructor Create(aEnumeratorTypeInfo: PTypeInfo);
    class function From(aEnumeratorTypeInfo: PTypeInfo): REnumerationEnumerator; static;
    function GetEnumerator: TEnumerationEnumerator;
  end;

implementation

{ REnumerationEnumerator }

constructor REnumerationEnumerator.Create(aEnumeratorTypeInfo: PTypeInfo);
begin
  Assert(aEnumeratorTypeInfo^.Kind = tkEnumeration);
  EnumeratorTypeInfo := aEnumeratorTypeInfo;
end;

{ REnumerationEnumerator }

class function REnumerationEnumerator.From(aEnumeratorTypeInfo: PTypeInfo): REnumerationEnumerator;
begin
  Result := REnumerationEnumerator.Create(aEnumeratorTypeInfo);
end;

function REnumerationEnumerator.GetEnumerator: TEnumerationEnumerator;
begin
  Result := TEnumerationEnumerator.Create(EnumeratorTypeInfo);
end;

{ TEnumerationEnumerator }

constructor TEnumerationEnumerator.Create(aEnumeratorTypeInfo: PTypeInfo);
var
  EnumeratorTypeInfo: PTypeInfo;
  EnumerationTypeData: PTypeData;
begin
  Assert(aEnumeratorTypeInfo^.Kind = tkEnumeration);
  EnumeratorTypeInfo := aEnumeratorTypeInfo;
  EnumerationTypeData := GetTypeData(EnumeratorTypeInfo);
  FMinValue := EnumerationTypeData.MinValue;
  FMaxValue := EnumerationTypeData.MaxValue;
  FValue := FMinValue-1;
end;

function TEnumerationEnumerator.GetCurrent: Integer;
begin
  Result := FValue;
end;

function TEnumerationEnumerator.MoveNext: Boolean;
begin
  Result := FValue < FMaxValue;
  if Result then
    Inc(FValue);
end;

end.

Note it contains both a Create constructor and From static class function.
This is by intent, I will explain this shortly.

The above unit gets great result when you call it in the main body of a simple console application like this:

program EnumerationEnumeratorDemo;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  TypInfo,
  EnumerationEnumerator in 'EnumerationEnumerator.pas',
  TraphicLightColorsUnit in 'TraphicLightColorsUnit.pas';

procedure ShowValueAsTraphicLightColor(Value: Integer);
var
  TraphicLightColorsTypeInfo: PTypeInfo;
begin
  TraphicLightColorsTypeInfo := TypeInfo(TTraphicLightColors);
  Writeln(Value, ' ', GetEnumName(TraphicLightColorsTypeInfo, Value));
end;

var
  Value: Integer;
  TraphicLightColor: TTraphicLightColors;
  TraphicLightColorsTypeInfo: PTypeInfo;
begin
  try
    Writeln('For in - create constructor - only works in your main program:');
    TraphicLightColorsTypeInfo := TypeInfo(TTraphicLightColors);
    for Value in REnumerationEnumerator.Create(TraphicLightColorsTypeInfo) do
    begin
      // TraphicLightColor := TTraphicLightColors(Value);
      ShowValueAsTraphicLightColor(Value);
    end;

    Writeln('For to:');
    for TraphicLightColor := Low(TTraphicLightColors) to High(TTraphicLightColors) do
    begin
      Value := Ord(TraphicLightColor);
      ShowValueAsTraphicLightColor(Value);
    end;

    Write('Press <Enter>');
    Readln;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

You get output like this:

For in – create constructor – only works in your main program:
0 Red
1 Orange
2 Green
For to:
0 Red
1 Orange
2 Green
Press

Until you start using that code in a procedure or function, then you consistently get Internal Error messages from the compiler.
The exact Internal Error is always of type F2084, but the subtype depends on the version of Delphi you have installed:

procedure ForIn_InternalCompilerError;
var
  Value: Integer;
  TraphicLightColorsTypeInfo: PTypeInfo;
begin
  TraphicLightColorsTypeInfo := TypeInfo(TTraphicLightColors);
  for Value in REnumerationEnumerator.Create(TraphicLightColorsTypeInfo) do
    ShowValueAsTraphicLightColor(Value);
end;
// Delphi 2007:
// [DCC Error] EnumerationEnumeratorDemoProcedures.dpr(49): F2084 Internal Error: C14125
// Delphi 2009:
// [DCC Error] EnumerationEnumeratorDemoProcedures.dpr(49): F2084 Internal Error: C15170
// Delphi 2010:
// [DCC Error] EnumerationEnumeratorDemoProcedures.dpr(49): F2084 Internal Error: C15700

Bummer. It actually caused me too loose interest in using for … in with enumerated types for a while.
Until today, when I needed a static class function returning an instance of another type.
Then I realized that there is not much difference between a constructor, and such function.

So in addition to the Create constructor, I added a From function like this:

    constructor Create(aEnumeratorTypeInfo: PTypeInfo);
    class function From(aEnumeratorTypeInfo: PTypeInfo): REnumerationEnumerator; static;

Lo and behold, this code now compiles fine:

procedure ForIn;
var
  Value: Integer;
  TraphicLightColorsTypeInfo: PTypeInfo;
begin
  TraphicLightColorsTypeInfo := TypeInfo(TTraphicLightColors);
  for Value in REnumerationEnumerator.From(TraphicLightColorsTypeInfo) do
    ShowValueAsTraphicLightColor(Value);
end;

This finally creates a work around the Internal Errors mentioned in QC65594.

I’m still a bit puzzled why this works with a static class function, but not with a constructor.
If anyone has a clue about that, please drop a note below.

–jeroen

16 Responses to “Delphi – for … in on enumerated data types”

  1. slovon11 said

    Great excercise and definitely full of great ideas but I think I’ll still write for … Low() to High(). Less typing :)

    But I fully agree that Delphi should support for TraphicLightColor in TraphicLightColor do syntax.

    • Uli Gerhardt said

      > Great excercise and definitely full of great ideas
      Indeed.

      > but I think I’ll still write for … Low() to High(). Less typing :)
      … and better performance, I suppose.

      > But I fully agree that Delphi should support for TraphicLightColor in
      > TraphicLightColor do syntax.
      That would be the best solution.

  2. CR said

    You’re doing far too much work, since for/in works on sets. At the risk of typing without trying first, this should work -

    var
      TraphicLightColor: TTraphicLightColors;
    begin
      for TraphicLightColor in [Low(TraphicLightColor)..High(TraphicLightColor)] do
          ShowValueAsTraphicLightColor(Ord(Value));
    end;
    

    Alternatively, try declaring all possible values as a set constant first – this probably looks a bit cleaner anyhow:

    const
      AllTraphicLightColors = [Low(TTraphicLightColors)..High(TTraphicLightColors)];
    var
      TraphicLightColor: TTraphicLightColors;
    begin
      for TraphicLightColor in AllTraphicLightColors do
        ...
    
    • jpluimers said

      Your solution is perfectly valid.
      I think the automatic version of your solition was what this QC report was suggesting the compiler should be able to do.

      • CR said

        ‘I think the automatic version of your solition was what this QC report was suggesting the compiler should be able to do.’

        Yes, it looks like it. Thinking about it, an ‘automatic’ version would be preferable too if it could have knowledge of how an enum type can have discontinuous elements.

      • jpluimers said

        @CR:

        Now that’s something I hadn’t thought about yet: enumerated types with discontinuous elements.
        If I remember correctly, they were introduced to support environments having lists of constants that had holes in them (correct me if I’m wrong). I don’t regard those as “true enumerated types” :-)

        Anyway, the “for … Low(…) to High(…)” construct wouldn’t produce correct results either, nor would the “for … in [Low(...) .. High(...)]“.
        Basically, getting it right would need some form of compiler support to fill a set with all possible elements from an enumerated type.
        Implementing it yourself by using RTTI might be possible, but I wonder if the RTTI in Pre-D2010 is even rich enough to do that using the TypInfo unit.

        Interesting what kind of collaborative thinking such a small experiment can raise!

        Thanks for all the comments so far; and keep them comming.

        –jeroen

  3. Xepol said

    I would agree that it should just work on enumerated types -> Do you have it in as a feature request in QC? I would toss a vote or two at it.

  4. I agree with Xepol. This ought to work normally on ordinals, including enums.

  5. François said

    What’s wrong with you people?
    Where’s good ol’ simple Pascal?
    ;-)


    var
    TraphicLightColor: TTraphicLightColors;
    begin
    for TraphicLightColor := Low(TTraphicLightColors) to High(TTraphicLightColors) do

    vs the “new” ideal

    var
    TraphicLightColor: TTraphicLightColors;
    begin
    for TraphicLightColor in TTraphicLightColors do

    OK, a bit lengthier, but still beats the workarounds IMO…
    Interesting reading though. Thanks!

    • jpluimers said

      Don’t you love research :-)
      And you are right: there is nothing wrong with the good ol’ way of using Low() and High().

      –jeroen

  6. This to me epitomises everything that is wrong with the software development tools space these days.

    A new language feature comes along and people feel they have to jump through hoops in order to apply it to everything, even things that really don’t need it as there are perfectly acceptable mechanisms that are less complex and in many cases less verbose than the “new” technique.

    At the same time the people wasting time on pursuing these attempts to apply new language features and new techniques to long-since solved problems protest that they are trying to use these new features in order to be more productive and solve actual business problems.

    It’s so tragic that they cannot see the contradiction in their own efforts that it just isn’t funny.

    Especially when they then turn around and ask for “fixes” to their new features that will enable them to be as productive as they used to be with the old ones.

    • jpluimers said

      Aren’t you generalizing a bit too much? Or did you have a really bad day?

      Sometimes it’s just plain fun to see what you can do with language features.
      It is also a way to learn where things are practical, and become impractical.

      It is not by accident that the code sample shows the plain old good working construct:

      var
      TraphicLightColor: TTraphicLightColors;
      begin
      for TraphicLightColor := Low(TTraphicLightColors) to High(TTraphicLightColors) do

      By showing the example, I was hoping that people would make that decision form themselves.
      Apparently, you make that decision for many people.

      Asking for the fix, was not for this particular feature. I just try to report any compiler bug, just as an ongoing QA practice: by letting the compiler developers know the weak points, they can assess which portions need (or need not) get more attention. As a by effect, knowing about certain bugs, sometimes eases the fix of other bugs.

      –jeroen

      • Yes, I was generalizing – as I said, this post *epitomises* a more general problem, I wasn’t picking on it especially.

        Having fun researching/playing around with stuff is of course, well, fun. But I don’t see any point in researching solutions to problems that are already adequately solved.

        But surely better to combine “fun research” with finding better ways to do something (and no, I don’t believe that there is a judgement call to be made in this specific case – an existing language construct that “just works” versus a whole slew of complicated code to achieve essentially the same thing – is not a question of “opinion”) or to solve a problem not yet solved at all.

        “Fun research” with no point is just, well, pointless. And as I say, in the general case (the pursuit of “productivity”) pointless research is the very antithesis of what proponents of these language features prosthelytize.

      • jpluimers said

        I think you are a bit hard in your judgement.

        The whole point about research is gaining knowledge. Most of that knowledge is not useful, at least not at the time of the research itself.
        Some researchers will never produce a useful result in their lifetime, but still the combined research of us all pays off.

        Sharing the results of research, and having others being able to decide what use they can make of it is a very important thing.
        I hope you can have some appreciation for that.

        –jeroen

  7. [...] Enumeration types. With some custom declarations, enumeration of “enumerated types” has been solved by Jeroen W. Pluimers [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 1,031 other followers

%d bloggers like this: