The .NET type system is rooted at System.Object. The Delphi/Win32 type system isn’t rooted (built-in simple types, records, and classes don’t have a common ancestor), and primitive types can’t/don’t implement interfaces, so someone in the newsgroups asked how to deal with generics which must perform operations on primitive types, without the use of IComparable, et. al. Here’s one way. I’m not sure if it’s the best way, but it works.
unit Unit5; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TAdder<T> = class public function AddEm(A, B: T): T; virtual; abstract; end; TIntAdder = class(TAdder<integer>) public function AddEm(A, B: integer): integer; override; end; TStringAdder = class(TAdder<string>) public function AddEm(A, B: string): string; override; end; TMath<T; A: TAdder<T>, constructor> = class public function AddTwoDigits(Left, Right: T): T; end; TForm5 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; var Form5: TForm5; implementation {$R *.dfm} procedure TForm5.Button1Click(Sender: TObject); var AddInt: TMath<integer, TIntAdder>; AddString: TMath<string, TStringAdder>; begin AddInt := TMath<integer, TIntAdder>.Create; try ShowMessage(IntToStr(AddInt.AddTwoDigits(2, 2))); finally AddInt.Free; end; AddString := TMath<string, TStringAdder>.Create; try ShowMessage(AddString.AddTwoDigits('2', '2')); finally AddString.Free; end; end; { TIntAdder } function TIntAdder.AddEm(A, B: integer): integer; begin Result := A + B; end; { TStringAdder } function TStringAdder.AddEm(A, B: string): string; begin Result := IntToStr(StrToInt(A) + StrToInt(B)); end; { TMath<T, A> } function TMath<T, A>.AddTwoDigits(Left, Right: T): T; var Add: A; begin Add := A.Create; try Result := Add.AddEm(Left, Right); finally Add.Free; end; end; end.
{ 28 } Comments
So in order to solve a problem that comes up with a valid use-case for generics, we have to go back to copy&paste programming…nice
I am really starting to love Scala (http://www.scala-lang.org/) which has beautiful solutions to those problems…
Oh an btw…could you please try out whether records can be generic, too?
type
TMyAdder = record
public function Add(V1, V2: T): T;
end;
Something like that…
Daniel, there’s no copy and paste there. The implementations are different.
Before unfavorably comparing code like this with a solution which uses, e.g., IComparable, remember that what I’m doing here is little different than what .NET, Scala/Java, etc., do for you. It’s unfortunate that I have to write it myself, but under the hood it’s little different.
Yes of course its technically different. As Delphi was not originally designed with those things in mind it is even understandable.
But we have to derive a new class for each value type that we want to use (even TDateTime and possible own TComplex or whatever). And that is exactly the problem that generics were to solve?
All I am saying it is a little sad that the Delphi implementation seems to be even more limited than the .Net one which is already limited. I once wrote a generic for a family of classes which all had to be specialized. It turned out to be a complete mess because the language (C#) was just too limited. In the end, a solution using "object"s everywhere might have been better.
But of course for simple things like TLists, TCollection etc those collections are already a great help.
Sorry for not sh*tting up
Wouldn’t it be possible to make TAdder .AddEm a non-static class function? In that case you could get rid of the constructor requirement and never have to create the class….
Daniel, there is no "generic" way to add two unspecified primitive types. If you overload the + operator and can use that overload in a generic, that’s great that you don’t have to rewrite the addition yourself, but there is still code for each type. In the end the code must be written somewhere. The difference with, e.g., Scala is that it’s already part of the library; you don’t have to write it yourself.
No, writing new classes for value types and value types alone is not "exactly the problem that generics were to solve."
Scala’s type system is more flexible than C#’s, true, but it’s *much* harder to understand. I see merit in both approaches, personally.
HI, Craig Stuntz.
After spending some time(about 4 hours) with new Delphi 2009, I have deduced that generics implementation is not actuality generics.
It is a very restricted implementation of generics.
1. Restriction of impicity or explicity types coercion of typed parameter.
2. Restriction of use operator IS And AS.
I of course understand this restriction on native side of Delphi. Becase there is no deterministic way convert T(typed parameter) to boxed type(managed type).
On IL side there is an "box " opcode.
3. Why R&D of Codegear(compiler guys) generate generics instantiation x86 code, but restrict using the binary and unary operators.
I know that there is no unityped IL
(CEQ instruction of IL is valid for INT, F, O, & types) opcode for compare OP in .NET.
Why this rescriction is carried to native implementation.
I of course understand the way to solve this
IComparer.
But suppose that collection(Tlist) is a big collection.
And I try to find out an element with indexOf method of Tlist.
Penalty of using gelegated implementation of compare operation(dispatch via VMT of interface) is very essential.
Why not use the inlined code in place where compare operation occurs.
And the next restrion of IComparer.is that an operands have to be of the same type(T)
And yours samples just confirm the problem of generics implementation of native Delphi.
The goal of using TMath is just that instation, remove operations are generalized.
But for every type you must provide an impementator of operation.
And you must provide valid pairs of typed parameters to TMath.
So you deterministicaly define pairs. So there is no generalization at all.
And you of course may translate yourscode (Add operation) to IComparer implementation sample in Generics.Defaults unit.
and reduce TMath> to TMath. But I see this implemenation and think that such impementation will be difficult for many Delphi progrmamers(>90% of delphi community).
But in any case that dispatch would be via VMT.
I think that Combination of .Net generics and C++ templates would be the best implemenation for class parameterization.
Sorry for my English. Sergey Antonov from Russia.
Sergey, be very careful of confusing "I can’t work the same way I do in C#" with "these aren’t really generics" and "certain corner case operations are hard" with "this is useless." I like C# a lot, but it’s not the one, true way of doing everything.
If you go into Delphi generics expecting it to behave precisely like C#, you’re going to find it’s a lousy version of C#. No surprise, since it doesn’t aim to be C# in the first place.
Craig Stuntz,
I just have said that CG guys copy C# (read .NET features) and it is not just my opinion. It is very sadly. I am not C# programmer, I’m Delphi man.
So, direct copy(with resctiction of type coercion) of generics implemenatation form C# with an unability of direct using operators on T is bad solution. IMHO.
Additionaly to my first post.
If you want to add values from different types or compare different types the IComparer will fails.
Why doesn’t to use at compile time overloads versions of appropriate operators in classes.
So for generics you would duplicate an implementation for overloads operators with wrapped class.
A one for any different combination of types.
And dont forget the penalty for indirect call via VMT on big collections.
It would be nice to have an operator constraint, but version 1 doesn’t have it. Perhaps in version 2? Without such a constraint, you can’t support the operator generically.
Putting all the arguments aside regarding the purity, or not, of Delphi generics when compared to C#, I think I would be disturbed to find code like this dealing with primitive types (with or without generics)
I think the overhead of supporting primitive type operations like this in generics would be appalling, and really only useful for relatively trivial/simple/small problems and datasets (or ‘educational’/'interesting’ examples of how to use generics). What’s worse, the syntax required to implement this very simple operation is pretty icky, and definitely not conducive to devs understanding what’s going on. So much for code maintainability
Raymond, be careful of missing the forest for one tree. This post illustrates one workaround for one specific limitation. As I said in the post, I’m not sure it’s the best way, but it works. It is not a general example of how to use generics, and doesn’t pretend to be.
Craig, I can’t realize how operator constraints may
assist us in gaining an ability to use operators with typed parameters.
The idea of constraints is to reduce superset of types to subset with identical behaviour, so any actions with this subset would be valid at compile time and it results in verified IL code.
The restrictions of using operators in generics in .NET is that at instantiation time(run time) there is no way to guarantee that method operators for any combination of types as parameters exists.
Of course is not impracticable task, but it
complicates implementation of .NET.
But this code would not be granted as safe code.
And additional disadvantage is that no more shared x86 code for different instantiations of same generic class with managed type typed parameters only(not value type typed parameters).
But we live in native world of Delphi, and all code(99,9999999%) is generated at compile time, except some hack code stubs in jungle of VCL.
So generated code will be always safe in manner of applying valid operations to types in parametrized class instantiations, if this code passes compile time.
Are there any ways to get to know of native Delphi language enchancement in next releases, except x64 and DPL?
Thank you.
Hi Craig,
In the line:
TMath, constructor> = class
can you tell me what the constructor keyword means, please? I can’t see a constructor method declared there or implemented for TMath later. Does it in effect specify T must be a type with a constructor, ie an object?
I just haven’t seen this notation before in articles I’ve read about Delphi’s generics.
Cheers,
David
Sorry, looks like the form at some of the symbols in the line I was quoting. It was the first line of the class declaration for TMath.
Cheers,
David
I appreciate it is a workaround. I suppose my message here is given this limitation, generics probably shouldn’t be going near primitive types (if only for the performance issue)!
Craig, here is the bug reproduction in generics implementation on Win32
Try this
TGenericRecord = record
procedure DoSomething(aParam: T);
end;
procedure TForm1.FormCreate(Sender: TObject);
var a:TGenericRecord;
b:integer;
begin
a.DoSomething(5);
end;
procedure TGenericRecord.DoSomething(aParam: T);
var a:integer;
begin
a:=integer((@aParam)^);
OK, I see that I’m going to have to write some more posts on this subject in order to cover some of the questions and assertions in comments. Briefly:
David, the constructor constraint specifies the signature of the constructor which the passed type argument must have. If memory serves, records can have constructors now, as well, so it’s not the same as a class constraint. In this case, I’m specifying a parameter less constructor.
Raymond, I’m now thinking this can be done without virtual methods, by passing an adder function instead of an outer class to the generic type.
Sergey, regarding the "bug," I’m honestly not sure if I care whether or not that code works.
Regarding a potential operator constraint feature, remember that implicit and explicit are also operators, so the compiler is entirely aware of when a class can or cannot be cast to a certain primitive type. One could specify, say, Implicit(T): integer as a constraint.
Good evening, Craig!
First of all about bug,
procedure Some"T".DoSomething(aParam: T);
var a:integer;
begin
a:=integer((@aParam)^); // Bug here, hello to the EDX register
….
end;
Asm code equavalent of assign op
…
Inner SEH FRAME
push $0046c650
push dword ptr fs:[eax]
mov fs:[eax],esp
a:=integer((@aParam)^); // Bug here, hello to the EDX register
Problem instruction - Assign OP
After this one we catch an Exception.
lea eax,quiet_dl
This instruction isn’t executed.
mov ebx,[eax]
Second.
Today I was trying to improve my implementation of C# Yield operator implementation in native Delphi.
You can find my free time research on santonov.blogspot.com.
But I encountered a problem that is an unabitility to use any asm chunks in parameterized classes.
Third. Recall to operators in parameterized classes.
You suggest an interestring solution.
Implicit(T):integer;
Or may be even Implicit(T):U.
And what about +,-,*,/ …. with different combination of all parameterized types.
That about the expression?
(U+T*Z-V*10*(cast to V) (U))/U
Class implementer may to be at a loss by this expression.
How many operator constraints has programmer to declare?
Why would not this responsibility move to compiler?
It is a power of native code.
And constraints is necessity of .Net to generate safe code.
But, in any case your idea is very good.
Sergey, in Jim McKeeth’s interview with Barry Kelly, Barry comments that generics are implemented in a part of the compiler which was originally developed for method inlining. As you may know, ASM cannot be used in an inlined method, either. Barry doesn’t discuss why in this specific interview, explicitly, but if you read between the lines of his comments about storing the parse tree of inlined methods in the DCU, I think you’ll figure it out. In short, it’s a limitation of the compiler, and one which isn’t likely to go away soon. To be honest, I think you’d have a pretty difficult time writing truly generic code in ASM anyway, but that’s a separate issue from the fact that you cannot do it at all right now.
How many generic type constraints must a programmer declare? As many as you need, for the code you have to write. If your list of type constraints starts looking like a class definition, and I would question how "generic" your type really is. Generally, one uses interfaces for such "heavily constrained" type arguments, but in Delphi we have to work around that for types which cannot use interfaces, i.e., primitive types.
Craig, thank you for you opinion.
> In short, it’s a limitation of the compiler, and one >which isn’t likely to go away soon.
The key words here is the limitation of the compiler. I think that the inner representation
of parse tree is only the inner representation.
Suppose the next code
SomeClassA=class(Some)
procedure abc;
end;
Why I can not write?
procedure SomeClassA.abc;
asm
end;
No dependency.
Where and what is the barrier?
What do you think about the relocable asm code representation in dcu?
Or the next one
procedure SomeClassA.abc;
var a,b:T;
begin
…
a:=b;
{relocable code.}
asm
lea eax,[a];
end;
…
b:=a;
..
end;
But!!! I think
Problem is that generics in native Delphi is a satelite of generics in Delphi.NET.
Resriction of using operators on T is just restriction of .NET.
Sergey, the "barrier" is the internal design of the Delphi compiler, written in the 90s without thought of inlining or generics, not a logical limitation on what could theoretically be done, outside of that specific design.
Craig, I try to answer your question.
If your list of type constraints starts looking like a class definition, and I would question how "generic" your type really is?
The class parametrization is just the way to define generic behaviour.
So, the expression
(U+T*Z-V*10*(cast to V) (U))/U is still generalized.
But for code generation there must be a guarantee to accomplish compilation process successfully.
For intermediate platforms, for generic IL code there is needs for additional info(constraints) to guarantee safe code.
At compile time that analysis of course may be carried out by compiler on source code.
So instantiation may valid. But at run time instantiation without constraints results in needs for reverse engineering of IL code to deduce constraints violation(an code of course).
But instantiation of .NET generics occurs at run time so no constraints, no safe code.
So for native platforms, there is no needs in any constraints, because all may be infered at compile time. But with one restriction there are no way to instantiate Generic class at run time with rational effort of course.
I disagree that .NET and Win32 Delphi are different WRT generic type safety.
Craing, I am very thankful to you.
Thank you again.
"David, the constructor constraint specifies the signature of the constructor which the passed type argument must have. If memory serves, records can have constructors now, as well, so it’s not the same as a class constraint. In this case, I’m specifying a parameter less constructor."
Thanks, Craig. I didn’t know that was possible. I can think of a number of ways that would be very useful.
"OK, I see that I’m going to have to write some more posts on this subject in order to cover some of the questions and assertions in comments."
I’d find another post interesting, and it would be great to have more information about some of this. Thanks for this one, though!
Adding yet another point on generics, in a cold post :
for the record, some sort of template coding was already possible in previous versions of Delphi :
-the "{$INCLUDE }" directive allows you to paste verbatim source code from some other file,
-Delphi allows you to give any name to any existing type.
So you can write :
UNIT MappingIntegerString;
INTERFACE
TYPE
_TEMPLATE_KEY_TYPE_ = Integer;
_TEMPLATE_ITEM_TYPE_ = String;
{$INCLUDE ‘GenericMappingInterface.inc’} //<- file where you write declarations using references to types names _TEMPLATE_KEY_TYPE_ and _TEMPLATE_ITEM_TYPE_
TMappingIntegerString = _TEMPLATE_MAPPING_TYPE_ //<- likewise, you choose in GenericMappingInterface.inc the convention for the name of the resulting class/record/…
IMPLEMENTATION
{$INCLUDE ‘GenericMappingImplementation.inc’} //<- file containing your template ipmplementation, using the same name conventions as above
END.
We are making extensive use of this feature with our Delphi projects.
The gain is pretty straightforward.
The main drawback with this method is that you have to explicitly create a separate Unit file for each instance of your template, since declaring 2 different versions of a template -e.g. MappingIntegerString and MappingIntegerInteger- in the same unit would result in a name conflict with the types.
With this method, you have the same debugging issues as templates in C++ : you potentially have different bugs for different instances of your template, caused for various reasons, and the compiler is obviously not adapted to check types and coherence in partial source files (in this case : the two .inc files used).
We are currently moving to Delphi 2009, and are pretty excited with the Generic type features for win32 (our projects are all for win32 platform).
However, we already saw several drawbacks :
-first, the CodeGear team did a terrific job at including generic types in a compiled language, but there are still too many bugs in the implementation (the first released version prevented to use pointers on generic types, there are still some strange compiling issues with method declaration…) ;
-second, we made some performance test, and going through *runtime* interfaces for generic types just doesn’t make it - we will still have to use our home brewed templates for performance issues in computation intensive parts.
Though I am really looking forward to use the neat genric syntax, I will go with Sergey : why, in a *compiled* language which produces compiled, typefree, static asm code do we have to pay a *runtime* overhead for static type constrains?
why, in a *compiled* language which produces compiled, typefree, static asm code do we have to pay a *runtime* overhead for static type constrains?
False premise. Constraints aren’t casts, and you don’t pay a runtime cost. Constraints are checked at compile time.
The cost you do pay for using an interface is due to (1) the virtual methods and (2) the reference counting, not the generic constraint. You can use a base object type as the generic constraint to avoid it. You will have exactly the same performance characteristics when using interfaces without generics.
I will say, though, that the performance problems associated with virtual method dispatch may be fixable in some future, improved version of the compiler. As an example, look at what the V8 JavaScript engine does.
{ 1 } Trackback
[...] type conversions, etc., on a type passed as a type parameter. In my last post on this subject, I suggested one possible solution, and experimented enough to show that it does work. But, as I noted in the post, I suspected it [...]
Post a Comment