I keep butting my head into the lack of an enumeration generics constraint.
I keep butting my head into the lack of an enumeration generics constraint.
I can't make generic arrays or set types from an enumeration, nor do operations on a variable of that type.
(Disclaimer: The code is for illustration only and may be incorrect or illogical)
TTaskFactory = class
type
TTaskClassArray: Array [T] of TTaskClass; //<-- illegal
TTaskSet: Set of T; //<-- illegal
private
class var FFactory: TTaskClassArray;
protected
FAllowedTasks: TTaskSet;
public
class procedure RegisterTask(const Id: T; aTaskClass:TTaskClass);
function MakeTask(const Id:T):TTask;
property AllowedTasks read FAllowedTasks write FAllowdTasks;
end;
class procedure TTaskFactory.RegisterTask(const Id: T; aTaskClass:TTaskClass);
begin
Log('Registered Task Id '+IntToStr(Ord(ID))); //<-- illegal
FFactory[Id] := aTaskClass; //<-- illegal
end;
function TTaskFactory.MakeTask(const Id:T):TTask;
begin
if Id in AllowedTasks //<-- illegal
then begin
if Assigned(FFactory[Id]) //<-- illegal
then Result := FFactory[Id].Create //<-- illegal
else raise EProgrammerNotFound.Create('Forgot to register');
end
else Result := nil;
end;
If only I could write:
TTaskFactory = class
TSoTaskType = (ttMakeItSo, ttSoBeIt, ... ttSoWhat)
TSoTaskFactory = class(TTaskFactory);
I am sure there are other examples, but I really miss being able to do
declarations and operations like these in a generic class:
Arrays, sets, low/high, ord, in, pred/succ, and related operations.
Yes, there are better ways to do class factories, and I use them - but the Enumeration constraint would enable so many possibilities.
I can't make generic arrays or set types from an enumeration, nor do operations on a variable of that type.
(Disclaimer: The code is for illustration only and may be incorrect or illogical)
TTaskFactory
type
TTaskClassArray: Array [T] of TTaskClass; //<-- illegal
TTaskSet: Set of T; //<-- illegal
private
class var FFactory: TTaskClassArray;
protected
FAllowedTasks: TTaskSet;
public
class procedure RegisterTask(const Id: T; aTaskClass:TTaskClass);
function MakeTask(const Id:T):TTask;
property AllowedTasks read FAllowedTasks write FAllowdTasks;
end;
class procedure TTaskFactory
begin
Log('Registered Task Id '+IntToStr(Ord(ID))); //<-- illegal
FFactory[Id] := aTaskClass; //<-- illegal
end;
function TTaskFactory
begin
if Id in AllowedTasks //<-- illegal
then begin
if Assigned(FFactory[Id]) //<-- illegal
then Result := FFactory[Id].Create //<-- illegal
else raise EProgrammerNotFound.Create('Forgot to register');
end
else Result := nil;
end;
If only I could write:
TTaskFactory
TSoTaskType = (ttMakeItSo, ttSoBeIt, ... ttSoWhat)
TSoTaskFactory = class(TTaskFactory
I am sure there are other examples, but I really miss being able to do
declarations and operations like these in a generic class:
Arrays, sets, low/high, ord, in, pred/succ, and related operations.
Yes, there are better ways to do class factories, and I use them - but the Enumeration constraint would enable so many possibilities.
Join the club
ReplyDeletethis looks more like java style coding than delphi coding, you could ditch the generic enumeration for say nativeint, and make a helper that converts nativeint into an enum type and back again, then take it from there.
ReplyDeletei feel like this requires a quote from rob pike: so, don't do that
So do I. but I think there is workaround in some cases, such as define TDictionary for the mapping, wrap some functions in TEnumHelper, TSetHelper, etc. Also, put a class constructor Create to check the type kind & fail fast. so it is still strong-typed and you can migrate them when primary type constraints are supported.
ReplyDeleteDorin Duminica Which is what I do - with the risks that imply. The constraint would allow for type safe code. What I miss the most when I do that, is High/Low.
ReplyDeletePlease bear in mind that "how to do a factory" is not the issue here. The issue is the lack of a constraint that allows us to limit the type to an enumeration, and use constructs and operators on that type or types derived from it.
ReplyDeleteDalija Prasnikar The "Lars Fosdal Head Butt Club" - Might give that one a miss ;-)
ReplyDeleteNicholas Ring Not that butt :P
ReplyDeletehttp://www.dictionary.com/browse/butting?s=t
Delphi isn't alone in this: https://wiert.me/2011/04/21/delphi-constraints-in-generics-rad-studio-xe-documentation-wiki/ but in C# you can sort of workaround it. Not in Delphi though.
ReplyDeleteYou just have to wait for the runtime errors I guess
ReplyDeleteDavid Heffernan True.
ReplyDeletehttps://quality.embarcadero.com/browse/RSP-12767 entered a while ago by yours truly :)
ReplyDeletecough templates cough
ReplyDeleteJeroen Wiert Pluimers The thing is that the restrictions generics have in .Net make sense, because being run-time instantiated they allow you to do certain things you cannot do with compile-time instantiation.
ReplyDeleteBut Delphi has all the restrictions run-time instatiation and none of the benefits of compile-time instantiation. It's the worst of both worlds...
Asbjørn Heid Templates are a horrible system, which is why no one outside of C++ uses them. (Good ideas get copied around and reused.)
ReplyDeleteWhat makes you say that generic constraints are useful because of runtime advantages? The advantages of constraints are at compile-time: they provide static typing to generics, rather than duck typing, (as is found in C++ templates,) which allows the compiler to easily and cleanly check for satisfiability up front, rather than spend an indefinite amount of time churning through template muck to figure out whether or not what you're trying to do is valid. (And then having a horrendously difficult time reporting what's wrong, when it isn't.)
Mason Wheeler I said restrictions, not constraints.
ReplyDeleteAnd I'm not familiar with another language that has compile-time generics, do you have some examples?
edit: by compile-time generics I mean compile-time instantiated generics, like Delphi.
Asbjørn Heid And what "restrictions" are you referring to? Lars Fosdal was talking about constraints.
ReplyDeleteMason Wheeler That you can't do template-like duck typing. I'm saying that restriction makes sense when the generics are instantiated at run-time, and instantiating the generics at runtime gives you some positives, for example the ability to instantiate based on a dynamic type (ie not compile-time-known), which counterbalances the restrictions.
ReplyDeleteOn the flip side, templates also have some restrictions due to being compile-time, but they also come the advantage of allowing for duck typing.
Delphi have all the restrictions of run-time generics, but not the duck typing advantage of templates.
Note I'm not saying Delphi needs touring complete templates... but if we have to live with the compile-time restrictions, we should get some form of duck typing in return.
Asbjørn Heid I think that's the problem: looking at duck typing as an advantage. To be perfectly honest, in all my years as a developer I have yet to find any scenario where the supposed "advantages" of duck typing even come close to outweighing the pain it causes even in normal development, let alone inside of a compile cycle!
ReplyDeleteMason Wheeler Maybe you've just been working on the wrong projects then. I found it very useful when I did C++ programming, and it's something I still miss a lot in Delphi.
ReplyDeleteAs for pain, yeah sometimes it was a bit of a hassle to track down template errors, but I don't duck typing itself is the primary reason for that, but rather a combination of touring complete templates and the rest of the C++ language allowing for quite a lot (compared to say Delphi).
Maybe it could be a compiler option you'd have to enable, ala POINTERMATH, for those who prefer the current state.
Mason Wheeler templates are used beyond C++. If love templates in Delphi for perf reasons. The fact that you don't have a use for them, and have an illogical hatred of C++ doesn't make templates useless.
ReplyDeleteDavid Heffernan Templates absolutely destroy performance in C++: they can make a compile literally last forever, due to Turing-completeness, and that's not even getting into the horrors inherent in trying to debug template code! As a developer, the massive productivity performance afforded by Delphi is far more valuable than whatever miniscule milliseconds templates can squeeze out of a program by inlining things here and there, saving a few cycles by removing the need to call some function. (On modern CPU architectures, do such things even have any noticeable impact at all anymore?)
ReplyDeleteMason Wheeler Allowing for duck typed generics in Delphi does not automatically mean the compile times would explode. C++ is a much more complicated language than Delphi.
ReplyDeleteMason Wheeler And if you think templates is all about shaving a few CPU cycles then you've clearly not used templates a lot.
ReplyDeleteMason Wheeler Compile time perf? I'm referring to runtime perf. How can you decide that's not important to me? Your irrational and uneducated hatred of C++ is getting in the way of your reasoning.
ReplyDeleteI am not a C++ user at all, but every now and then I find myself thinking C++ templates would be perfect fit for some code.
ReplyDeleteI just want a few type constraints to remove the constraints on the code I want to write.
ReplyDeleteMason Wheeler I miss C++ templates too. Sure there can be bad examples, but overall they are fantastically useful. Even a simple template class can be a lot more powerful than Delphi's generics and a lot of that is due to the fact you can write code based on the type it's meant for. You can't do that in Delphi, as the enum example here illustrates. IMO that should compile and if a non-enum type was passed to the generic, then and only then should there be a compile error.
ReplyDeleteAnyone who doubts the power and utility of templates should spend some time working with the eigen library and then consider how to implement same using generics.
ReplyDeleteDavid Heffernan On the contrary, my education is precisely why I hate C++: I was forced to use it throughout my college years, and I learned far more about how it works (and how broken it is!) than you seem to believe.
ReplyDeleteMason Wheeler I guess you didn't learn it well because most of what you write on the topic is wrong. Try eigen and then see how that would map to a language without templates.
ReplyDeleteDavid Heffernan And what is "eigen"? I've never heard anyone other than yourself ever even mention this library. Whatever it is, it must not be all that significant...
ReplyDeleteMason Wheeler That's your attitude in a nutshell. If you've not heard of it, it cannot be significant. How can you hope to learn anything new that way?
ReplyDeleteDavid Heffernan That's simple enough: when I hear enough people talking about something new, I know it's significant enough to learn about.
ReplyDeleteFor anyone else who might be wondering, Eigen is a very popular[1] library for for linear algebra, numerical solvers and related stuff in C++. It uses templates to allow for very elegant yet highly optimized code.
ReplyDelete[1]: http://eigen.tuxfamily.org/index.php?title=Main_Page#Projects_using_Eigen
Mason Wheeler And how is that strategy working out for you. Your knowledge of C++ is woefully inadequate. Looks to me like it's time to reassess.
ReplyDeleteDavid Heffernan On the contrary, I know everything about C++ I will ever need to know: it inherited C's buffer overflow problems, which are not easily fixed, which continually cause serious damage measured in billions of dollars every year, and therefore should not be used under any circumstances.
ReplyDeleteThat's the only thing any developer needs to know about C or C++, because if you know that the language makes it easy to do the wrong thing, to do something so horrendously wrong that it can end up destroying your reputation and ruining the lives of your users (in the event of a major data breach, for example,) and makes it so difficult to do the right thing that the most experienced developers in the world continue to screw it up program after program, year after year, decade after decade, then literally nothing else matters.
Mason Wheeler sadly your "knowledge" is wrong. With your mind closed you cannot hope to learn.
ReplyDeleteC++ has come a long way, but I still find the syntax to offer little to no advantages in code clarity.
ReplyDeleteLars Fosdal Personally I find inline variable declarations (with proper scoping), const correctness and "true" references to really help writing clear code, as in I find them really helpful when reading the code later, and I miss those a lot in Delphi.
ReplyDeleteI will however agree that you can write some really god awful stuff in C++ if you try :)
Asbjørn Heid I will concede that inline vars with a local scope is a good thing.
ReplyDeleteIt's easy to write bad code in any language. The idea that C++ is susceptible to buffer overruns and Delphi is not is quite frankly idiotic.
ReplyDeleteDavid Heffernan Then why do buffer overruns only tend to happen in C/C++ code? Heck, the only time I've ever run into one in Delphi code came from an improper declaration of external C DLL code.
ReplyDeleteSure, it's possible to create a buffer overrun in Delphi, but in practice it doesn't tend to happen. In C, it's easy, it's the default behavior, and you have to go out of your way to do it right, while in Delphi you have to go out of your way to do it wrong. Can you honestly say that's not true?
Mason Wheeler Yes, that is not true. You don't need to go out of your way at all. Just read beyond the end of the buffer. And I'm talking about C++ which is a different language from C.
ReplyDeleteMason Wheeler How about his nice Delphi code. You can have buffer overruns in Delphi without going out of your way to do so.
ReplyDeletehttp://qc.embarcadero.com/wc/qcmain.aspx?d=119279
http://qc.embarcadero.com/wc/qcmain.aspx?d=126740
For those that cannot read private report it is about Windows GetClassName being called with wrong parameters throughout VCL and FMX potentially creating buffer overrun (Fixed in XE7, and been sitting there probably since Delphi 2009)
For example:
function GetSysWindowClassName(Window: HWND): String;
var
sClassName: PChar;
begin
GetMem(sClassName, 256);
try
GetClassName(Window, sClassName, 256);
Result := String(sClassName);
finally
FreeMem(sClassName, 256);
end;
end;
Dalija Prasnikar The use of PChar is going out of your way; in idiomatic Delphi code, we have a real string type that prevents this sort of problem. This is an example of a problematic interoperation with external C code, as I mentioned earlier.
ReplyDeleteMason Wheeler This demonstrates precisely my point about your lack of knowledge. If you knew C++ then you'd know that it has a string type.
ReplyDeleteBut since, by your own statements, you've already made your mind up, how can you learn this?
David Heffernan I know all about std::string. I also know that it can't be used in interop with external code. I also know that, std::string or no std::string, we get buffer overflow problems in widely-used C++ code (such as operating systems) on a regular basis.
ReplyDeleteWhen a string literal becomes a std::string rather than a char*, that would be real progress, but it can't be done because backwards compatibility with decades of broken C code.
Mason Wheeler You really have no idea what you are talking about. Operating systems are not written in C++. And std::string can be used in interop. The c_str() method is for that purpose. Or operator[] when writing to a string, although for that purpose I'd probably use std::vector.
ReplyDeleteWell written C++ code is perfectly fine with literals because it never operates on them as C strings. For instance:
std::string str(L"foo");
You are deeply prejudiced based on incomplete and inaccurate "knowledge".
Now, my original point was that a library like eigen is only possible due to the power of templates. It cannot be written with generics.
Whilst I personally much prefer writing code in Delphi, performance reasons make moving to C++ attractive. Were I to do so I would see very significant improvements in my app's performance. It frustrates me that my chosen language holds me back. I want more.
David Heffernan Where in the world did you get the idea that operating systems aren't written in C++? It's all over the place in Windows, and most of the rest is C, which doesn't even have a safe string. The Linux kernel is C, and most of the userland stuff is C or C++. And using .c_str() is not using std::string in interop, it's using char* in interop and throwing conversions around everywhere because std::string can't be used in interop.
ReplyDeleteSame with your example of a constructor call that passes in a char*. My point isn't that those conversions aren't available, but rather that they're necessary, and if you don't understand why that's a bad thing, then it seems my knowledge is more complete and accurate than yours.
What are languages are OSs written in?
ReplyDeletehttp://stackoverflow.com/questions/580292/what-languages-are-windows-mac-os-x-and-linux-written-in
Mason Wheeler Faith is no grounds for such a discussion.
ReplyDeleteDavid Heffernan ...huh?
ReplyDeleteMason Wheeler Faith. When one believes something irrespective of evidence. You have no evidence that C++ is more prone to buffer overruns. You just believe it. Creating a string from a literal in C++ is not a source for buffer overruns. But you don't care about the facts. Your mind is already set.
ReplyDeleteDavid Heffernan The evidence speaks for itself: programs written in C++ regularly suffering disastrous data breaches due to buffer overruns, and hurrying to put out patches to prevent exploits on a monthly basis. What greater evidence do you need than that? My mind is already set because of the evidence, not in spite of it!
ReplyDeleteMason Wheeler Well written C++ is no more susceptible to overruns than well written Delphi. You seem confused between C and C++ amongst other things. Where are these well written C++ programs that are built on the STL and have buffer overruns. You see defects in Windows and Linux and OpenSSL etc. and think that means that C++ is defective. That's comical. I've said enough.
ReplyDeleteDavid Heffernan That's the whole point: "Well written C++" does not exist! It's a theoretical construct that can be approached in practice, but the language is stacked against you, making it so easy to get things wrong that even the most experienced developers keep writing buffer overflows year after year, decade after decade, program after program.
ReplyDeleteYou see defects in some of the most prominent C++ products and think that somehow means the language is not defective? That's insane.
Mason Wheeler You are wrong. Well written C++ exists. The programs with defects to which you refer are mostly written in C. Poorly written code exists in Delphi. Its just as easy to do in Delphi.
ReplyDeletePointing to stats is naive because of the relative rarity of Delphi code in headline programs. Which major program which gets airplay when defects are found is written in Delphi? Name one.
Anyway I've said my bit. I'll change my mind when you show a technical reason to back your claims. With reference to the languages.