The Wiert Corner – irregular stream of stuff

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

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

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

    Join 4,262 other subscribers

Delphi – for … in on enumerated data types

Posted by jpluimers on 2009/10/27

I like [Wayback/Archive] 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 [Wayback/Archivestructured 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 [Wayback/Archiveexpects a collection type.
A request for [WayBack/Archive] 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 [WayBack/Archive] 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 <Enter>

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 [WayBack/Archive] 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.

Edit 20230314: reference to StackOverflow where I used this blog post in an answer at [Wayback/Archive] enums – Iterate through items in an enumeration in Delphi – Stack Overflow (thanks [Wayback/Archive] JosephStyons for asking).

–jeroen

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

  1. […] far for consistency (I will blog more on that […]

  2. […] G. Mitzen posted a very interesting (and elaborate <g>) comment on a post from me in 2009: Delphi – for … in on enumerated data types « The Wiert Corner – […]

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

  4. 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

    • Joseph G. Mitzen said

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

      And this to me stands out as to what’s wrong with the Delphi development community these days. :-( We’ve become so isolated from the rest of the programming world that we tend to look upon each new advance of computer science as a personal attack against us and we thus need to think of reasons why that improvement’s really bad and why our old ways are better.

      >A new language feature comes along and people feel they have to jump through hoops in order to apply it to
      >everything,

      Yes, Jolyon. It’s called “consistent design”. Every time there’s an improvement in the language it’s the burden of EMBT to look back through the entire language, VCL and nowadays FireMonkey and ask “How can we use this feature to make the language easier/better/cleaner/smaller/more consistent”? When we don’t, we end up with half a dozen ways to read a file – Text File, File of, BlockRead, TStream, TFile, etc. We end up with iterators that don’t iterate everything that’s iterable. We end up with lots of “X works for Y but not for Z for no logical reason” special rules that drag a language down and make it harder to use and harder to learn because of all the special cases.

      >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.

      Consistency, consistency, consistency. “There should be one – and preferably only one – obvious way in which to do it.” We’re not striving for “perfectly acceptable”. We’re striving for the best language possible. If the new way’s better or more generally useful, yank the old ways out and apply the new way everywhere.

      Python’s as old as Delphi yet there’s just one way to open a file in 20 years – “open”. And their “open” can do everything our half a dozen ways combined can, and then some. Iterators? “It was Guido’s act of genius to have iterators – I just said let’s put them everywhere!” – Raymond Hettinger. Lists enumerate, as do sets, dictionaries, file reading, database query results, and everywhere else in which it makes sense.

      The Australian Delphi User Group has a blog article that mentions all the things we still can’t enumerate even after eight years.

      http://members.adug.org.au/2013/01/08/for-in-enumeration/

      > Enumeration types.

      Python’s just adding enumeration types now, but guess what? They’re iterable. They’re also going to be more powerful than ours, which don’t seem to have been touched since the days of Turbo Pascal and don’t work with RTTI if you use custom numbering. They’re also doing them as classes, which means they can even have methods and much work (over 1,000 public email posts on two mailing lists and a few hundred private emails over several months) went into making sure they work *consistently* with all the other elements of the language.

      > Set inversion. Currently, there is no way to for-in enumerate over members NOT in set. Wouldn’t it be handy if we
      >had a generic function to invert sets.

      Python does.

      > Character Streams. Ever wanted to parse a text file character by character? Wouldn’t it be nice if we could use
      >for-in?

      Python can.

      > Reverse order. Have you ever wanted to traverse in reverse order? How useful would this be?

      Python has a standard function, “reversed”, than can reverse any iterable object.

      > TDataset – How many times have you written a while-not-eof loop? For us older developers, the number is
      >probably astronomical.

      Iterating works with any database driver that meets Python’s DB-API 2.0 standard, which generally means all of them.

      > Predicates. Ever wanted to traverse a collection, but only for those members that met some specified condition.

      Python has list comprehensions so one can do something like

      [word.lower() for word in wordlist if ‘el’ in word]

      So Python’s putting massive effort into making their language consistent and easy to learn and using the “principle of least astonishment” so that if you learn how to do something it works everywhere else it would make sense to, and we’re complaining if anyone wants to make our language more consistent, more modern or easier to use. :-( That’s why our language no longer gets called “beautiful”. :-(

      >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.

      Yes they are and they do. Iteration is a powerful tool that makes things simpler, but won’t be fully realized until everything that can iterate in Delphi does iterate, especially database results. Until then it’s like a military refusing to cut programs in the face of budget cuts and ending up with half a carrier battle group, half an air wing, etc. – a carrier but no protection and half its capability to project force, etc. It’s only when Delphi’s features reach “full strength” will you see their potential.

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

      It’s tragic IMHO that some people want to keep the language outdated or will argue that there’s nothing wrong with enumerators that don’t iterate, sets that can’t hold values greater than 255, lack of a step clause in the for loop, generics that don’t work with first class functions and procedures, helpers for built-in types that disappear if you add your own, etc. because someone once posted a 75-line workaround on a blog somewhere. We can’t compete with other languages if that’s our approach.

      Google employed a “project Butter” to improve the “fit and polish” of Android and find all the small areas of Android where things didn’t work smoothly, consistently or correctly and fix them to avoid “death by a thousand cuts” of the end user experience. Delphi is in MAJOR need of its own Project Butter, in the language, in the VCL (applying new features throughout the framework wherever they make sense), and in the IDE (which is a whole other issue).

      >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.

      Computer science has achieved “advances”. No one was more productive with the old ways or else the new ways wouldn’t exist. Iteration is tried and proven and shoving it into a corner and still using our old for loops (you don’t even need a for..to loop if your language has for..in!) with our initialized (at the top of the code block) index variables just isn’t going to cut it in 2013.

      No matter how one looks at it, it’s simply poor work on the part of EMBT to only partly implement a new feature and then leave it like that for years, just present enough to check off a box on a feature matrix but not enough to be anywhere near useful. This then causes some people to doubt the feature ever was useful anywhere and want to go back to “the old ways”. This does us no favors either way. EMBT has to learn follow-through and finishing the work they start before rushing off to add more half-done features to justify another round of upgrade sales.

  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. I agree with Xepol. This ought to work normally on ordinals, including enums.

  7. 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.

  8. 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

  9. 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.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.