Jump to content
Erik@Grijjy

The Curiously Recurring Generic Pattern

Recommended Posts

14 hours ago, Erik@Grijjy said:

In the category Fun with Generics: https://blog.grijjy.com/2022/01/25/crgp/

Very interesting article, I love C++ Delphi :classic_biggrin:

Especially the use case samples are great, where I think this could make it possible to be used in a nested way, what I consider for some time.

Something like that:

var Builder := THtmlStringBuilder.Create;
Builder
.Html
.Header.Enter
.Header.Text( 'In header' )
.Header.Leave
.Div.Enter
.Div.Id(    'main'   )
.Div.Color( clYellow )
.Div.Text( 'In div'  )
.Div.Leave
.Footer.Enter
.Footer.Text( 'In footer' )
.Footer.Leave
;
WriteLn(Builder.Data);

I think that should be possible and should make sense too, I have to check that.

 

What I'm a little unsure is, how stable and reliable this "self reference trick" is tested under different Delphi versions.

Can-I-Use it without any unexpected quirks, or do I better wait and see if this is still running in the next version(s) ?

 

 

Edited by Rollo62

Share this post


Link to post
9 hours ago, Rollo62 said:

Very interesting article, I love C++ Delphi :classic_biggrin:

Especially the use case samples are great, where I think this could make it possible to be used in a nested way, what I consider for some time.

Something like that:

Thanks!
Yes, the nested case is definitely possible. Your THtmlStringBuilder class would probably have to maintain some sort of state stack so you can match your Enter and Leave calls. This will also ensure you output proper (X)HTML.

 

BTW: This pattern isn't a trick and doesn't depend on any compiler quirks. It is just a feature of generics in Delphi (and possibly other OOP languages). I remember using this pattern first with Delphi at least 5 years ago, and I don't see any reason why future compilers would change this behavior. It may not be supported by the very first (few) versions of Delphi that introduced generics, but I have no way to test that.

Share this post


Link to post
31 minutes ago, Erik@Grijjy said:

BTW: This pattern isn't a trick and doesn't depend on any compiler quirks. It is just a feature of generics in Delphi (and possibly other OOP languages). I remember using this pattern first with Delphi at least 5 years ago, and I don't see any reason why future compilers would change this behavior.

Thanks for clarification, I hadn't recognized from your article that this feature is D5 compatible, sounded D11 related to me, and it looked a little alien to me anyway and ringing my alarm bells :classic_biggrin:

Much better that this pattern has such a long history, I was not using D5, but BCB5 at those days, so I'm still learning new features every day.

  • Like 1

Share this post


Link to post

Ok, after reading the article, it makes more sense.

 

It will take some pondering to come up with legitimate use cases.

 

The article ends with:

 

Quote

Arguably the most common use case for this pattern in the C++ world is to implement a form of static polymorphism. This form of polymorphism resolves methods calls at compile-time instead of run-time. This avoids the overhead of virtual methods and can therefore improve performance a bit. But since this requires a C++ specific template feature, it cannot be replicated in Delphi (although I would love to be proven wrong here).

 

A while back, I was trying to translate a Swagger spec into some Delphi code, and I ran into some issues that this might solve.

 

The Swagger spec is read at run-time and the program generates Dephi code that gets compiled later on.

 

The problem is that the typing isn't really known at run-time when you're reading the Swagger spec. But emitting code that resolves it at a subsequent compile time without having to know it when it's emitted might solve the problem...

 

Edited by David Schwartz
  • Like 1

Share this post


Link to post
10 hours ago, Erik@Grijjy said:

It may not be supported by the very first (few) versions of Delphi that introduced generics, but I have no way to test that.

FYI, XE4 can compile the tests, after removing the inline var statements.

Edited by Edwin Yip

Share this post


Link to post

Doesn't using classes, abstract classes, generics, hidden properties, class constructors, and of course a typecast of sorts anyway for such a simple problem make you want to throw up your hands in despair and swear to never use static typing again?

  • Haha 2

Share this post


Link to post
9 hours ago, Joseph MItzen said:

never use static typing again

The issue is not static typing but the ridiculous amount of ceremony required.

  • Like 1

Share this post


Link to post
13 hours ago, Edwin Yip said:

FYI, XE4 can compile the tests, after removing the inline var statements.

Great! Thanks for testing!

Share this post


Link to post

This is a good technique, but speaking of generic collections, honestly, after using it in several projects, now except for dictionaries, I've gone back to using the non-generic collections from `System.Contnrs`.

In order to make it comfortable using the object lists, all I have to do is to override the `Items` property for a specific class, like this:

  TAddressList = class(TEyObjectList) // TEyObjectList is an enhanced descendent of TObjectList
  protected
    function GetItem(Index: Integer): TAddress;
    procedure SetItem(Index: Integer; aObj: TAddress);
  public
    property Items[Index: Integer]: TAddress read GetItem write SetItem; default;
  end;

Making such derived class is a 10-seconds operation with the help of MMX's class templates and Find-and-Replace.

 

Maybe it's just me, but I really don't find too much cases where a `IList` can serve both the objects and string items (for example) well.

Maybe for library writers it's good to have a collection to rule them all, but for users of libraries, specificity is a good thing.

Another burden is the lame support of generics of the Delphi compiler...

Share this post


Link to post
On 1/27/2022 at 9:43 PM, Edwin Yip said:

This is a good technique, but speaking of generic collections, honestly, after using it in several projects, now except for dictionaries, I've gone back to using the non-generic collections from `System.Contnrs`.

In order to make it comfortable using the object lists, all I have to do is to override the `Items` property for a specific class, like this:


  TAddressList = class(TEyObjectList) // TEyObjectList is an enhanced descendent of TObjectList
  protected
    function GetItem(Index: Integer): TAddress;
    procedure SetItem(Index: Integer; aObj: TAddress);
  public
    property Items[Index: Integer]: TAddress read GetItem write SetItem; default;
  end;

Making such derived class is a 10-seconds operation with the help of MMX's class templates and Find-and-Replace.

 

Maybe it's just me, but I really don't find too much cases where a `IList` can serve both the objects and string items (for example) well.

Maybe for library writers it's good to have a collection to rule them all, but for users of libraries, specificity is a good thing.

Another burden is the lame support of generics of the Delphi compiler...

 

You're saying this is simpler than saying something like:

 

var TAddressList = TList<TAddress>;

// or

var TAddressList = TList<TEyObjectList>;

 

I don't understand the last paragraph at all. What are you referring to?

 

Edited by David Schwartz

Share this post


Link to post
1 hour ago, David Schwartz said:

 

You're saying this is simpler than saying something like:

 


var TAddressList = TList<TAddress>;

// or

var TAddressList = TList<TEyObjectList>;

 

I don't understand the last paragraph at all. What are you referring to?

 

Hi David, for simple use cases, the generic collections are simpler.

 

However, please consider situations where you need to add a bunch of methods such as CopyFromLeft, CopyFromRight, Each, GetPropertyValuesByNameAsCSV,  and so on, to the enhanced list class.

In this case I have two options, and I'll outline the pros and cons:

  • Inherit from the generic TObjectList<>
    • Pros:
      • Can eliminate some of the typecasting, but not all, without techniques such as Curiously Recurring Generic Pattern the OP is presenting. 
    • Cons:
      • From time to time, the compiler emits strange internal error and I'll have to restart the IDE.
      • bloated EXE sizes.
  • Inherit from the non-generic TObjectList
    • Pros:
      • No strange compiler errors.
      • No strange bloated EXE sizes.
    • Cons:
      • In order to eliminate all typecasting for the list users, you'll have to make a derived class for each object. 

 

That being said, I might be wrong, but my gut feeling is using the non-generic TList/TObjectList is more comfortable, especially without the need to constantly restart the IDE.

 

By IList, sorry for not being clearer, it's from Spring4D which is excellent, so it just occurred to me. I should have said TList<>.

Edited by Edwin Yip
  • Thanks 1

Share this post


Link to post
Quote

From time to time, the compiler emits strange internal error and I'll have to restart the IDE.

It happens to me too. In that case, I run the build. I have small projects. Suddenly everything is fine. Up to two:classic_biggrin:
I don't know if this is your case either.

Edited by Stano

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×