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.

Comments

  1. this 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.
    i feel like this requires a quote from rob pike: so, don't do that

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

    ReplyDelete
  3. Dorin 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.

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

    ReplyDelete
  5. Dalija Prasnikar The "Lars Fosdal Head Butt Club" - Might give that one a miss ;-)

    ReplyDelete
  6. You just have to wait for the runtime errors I guess

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

    But Delphi has all the restrictions run-time instatiation and none of the benefits of compile-time instantiation. It's the worst of both worlds...

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

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

    ReplyDelete
  9. Mason Wheeler I said restrictions, not constraints.

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

    ReplyDelete
  10. Asbjørn Heid  And what "restrictions" are you referring to?  Lars Fosdal was talking about constraints.

    ReplyDelete
  11. Mason 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.

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

    ReplyDelete
  12. 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!

    ReplyDelete
  13. Mason 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.

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

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

    ReplyDelete
  15. David 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?)

    ReplyDelete
  16. Mason 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.

    ReplyDelete
  17. Mason Wheeler And if you think templates is all about shaving a few CPU cycles then you've clearly not used templates a lot.

    ReplyDelete
  18. Mason 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.

    ReplyDelete
  19. I 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.

    ReplyDelete
  20. I just want a few type constraints to remove the constraints on the code I want to write.

    ReplyDelete
  21. Mason 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.

    ReplyDelete
  22. Anyone 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.

    ReplyDelete
  23. David 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.

    ReplyDelete
  24. Mason 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.

    ReplyDelete
  25. David 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...

    ReplyDelete
  26. Mason 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?

    ReplyDelete
  27. David Heffernan That's simple enough: when I hear enough people talking about something new, I know it's significant enough to learn about.

    ReplyDelete
  28. For 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.

    [1]: http://eigen.tuxfamily.org/index.php?title=Main_Page#Projects_using_Eigen

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

    ReplyDelete
  30. David 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.

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

    ReplyDelete
  31. Mason Wheeler​ sadly your "knowledge" is wrong. With your mind closed you cannot hope to learn.

    ReplyDelete
  32. C++ has come a long way, but I still find the syntax to offer little to no advantages in code clarity.

    ReplyDelete
  33. Lars 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.

    I will however agree that you can write some really god awful stuff in C++ if you try :)

    ReplyDelete
  34. Asbjørn Heid​​ I will concede that inline vars with a local scope is a good thing.

    ReplyDelete
  35. It'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.

    ReplyDelete
  36. David 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.

    Sure, 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?

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

    ReplyDelete
  38. Mason Wheeler How about his nice Delphi code. You can have buffer overruns in Delphi without going out of your way to do so.

    http://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;

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

    ReplyDelete
  40. Mason 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. 

    But since, by your own statements, you've already made your mind up, how can you learn this?

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

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

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

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

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

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

    ReplyDelete
  44. Mason Wheeler​ Faith is no grounds for such a discussion.

    ReplyDelete
  45. Mason 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.

    ReplyDelete
  46. David 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!

    ReplyDelete
  47. Mason 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.

    ReplyDelete
  48. David 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.

    You see defects in some of the most prominent C++ products and think that somehow means the language is not defective?  That's insane.

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

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

    ReplyDelete

Post a Comment