PeterPanettone 157 Posted July 13, 2020 Today in the shower I had an idea - wouldn't Open Type Arrays be a useful thing? What if we could write: procedure DoSomething(const AParam: [string, Integer]); begin if AParam is string then ShowMessage(AParam) else if AParam is Integer then ShowMessage(IntToStr(AParam)); end; One could argue that if we want flexibility we could use overloaded procedures. But if we have procedures with many different parameters then we had to write a lot of overloaded procedures - Open Type Arrays would simplify things a lot. What do you think? Share this post Link to post
David Heffernan 2345 Posted July 13, 2020 This seems worse than method overloading to me. There you have compile time branching. Here you have to perform runtime type checking. 5 Share this post Link to post
dummzeuch 1505 Posted July 13, 2020 56 minutes ago, David Heffernan said: This seems worse than method overloading to me. There you have compile time branching. Here you have to perform runtime type checking. Not necessarily. The compiler could generate a set of overloaded procedures for this. 1 Share this post Link to post
david berneda 19 Posted July 13, 2020 Some languages offer union types to do this, for example in Typescript: https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types 1 Share this post Link to post
Cristian Peța 103 Posted July 13, 2020 For this case will work with Variant. procedure DoSomething(const AParam: Variant); begin ShowMessage(AParam); end; 3 Share this post Link to post
Mahdi Safsafi 225 Posted July 13, 2020 For dynamic language like typescript ... sure they're designed to work like that. Elsewhere (Delphi), this is going to be awful because you're checking the type at runtime, moreover you are mixing reference(string) with integer ... how are you expecting from the compiler to pass argument for your call (i.e 8 bytes and 4 bytes) ? Using union may solve the problem but still there is a drawback to check for the type. So I don't think it is a good idea at all. Share this post Link to post
David Heffernan 2345 Posted July 13, 2020 3 hours ago, dummzeuch said: Not necessarily. The compiler could generate a set of overloaded procedures for this. You mean with the code from OP? 1 Share this post Link to post
dummzeuch 1505 Posted July 13, 2020 1 minute ago, David Heffernan said: You mean with the code from OP? Yes Share this post Link to post
David Heffernan 2345 Posted July 13, 2020 1 minute ago, dummzeuch said: Yes I would absolutely not want syntax like that. If that's the way you want to go you can do it today with generics. In an ideal world I'd like to see the generality offered by C++ templates though. That would allow algorithmic programming that is just too painful with generics. Share this post Link to post
Uwe Raabe 2057 Posted July 13, 2020 3 hours ago, dummzeuch said: Not necessarily. The compiler could generate a set of overloaded procedures for this. And copy the method body in each? Well, it could split that code and put the pieces into the appropriate overload. On the other hand, that is exactly what a developer would do - and that is way more readable and maintainable (at least IMHO). 3 Share this post Link to post
Stefan Glienke 2002 Posted July 13, 2020 (edited) 4 hours ago, Cristian Peța said: For this case will work with Variant. Variant will accept everything and their dog which is hardly what is wanted here. @PeterPanettone This is not in any form an array but as @david berneda rightly said a union. See https://en.wikipedia.org/wiki/Union_type We already have such things in Delphi: TVarRec, Variant, TValue. However none of them can be specified to only accept a certain amount of types. If you want something like that then write a custom record type with implicit operators and a TValue as internal storage (saves you from taking care of mixing unmanaged and managed types in a proper way) Just for the lulz: {$APPTYPE CONSOLE} uses SysUtils, Rtti; type Union = record fvalue: TValue; class operator Implicit(const value: string): Union; class operator Implicit(const value: Integer): Union; class operator Implicit(const value: Union): string; class operator Implicit(const value: Union): Integer; function IsType<T>: Boolean; end; procedure DoSomething(const AParam: Union); begin if AParam.IsType<string> then Writeln('string: ', string(AParam)) else if AParam.IsType<Integer> then Writeln('integer: ', IntToStr(AParam)); end; class operator Union.Implicit(const value: Integer): Union; begin Result.fvalue := value; end; class operator Union.Implicit(const value: string): Union; begin Result.fvalue := value; end; class operator Union.Implicit(const value: Union): string; begin Result := value.fvalue.AsString; end; class operator Union.Implicit(const value: Union): Integer; begin Result := value.fvalue.AsInteger; end; function Union.IsType<T>: Boolean; begin Result := fvalue.IsType<T>; end; begin DoSomething(42); DoSomething('Hello world'); end. If you want to go funky then make it a generic type with 2, 3, 4, ... generic type parameters 😉 Edited July 13, 2020 by Stefan Glienke 2 Share this post Link to post
PeterPanettone 157 Posted July 13, 2020 2 hours ago, Uwe Raabe said: Well, it could split that code and put the pieces into the appropriate overload. Why make double work? The employer would like it if you need only half the time. Share this post Link to post
David Heffernan 2345 Posted July 13, 2020 53 minutes ago, PeterPanettone said: Why make double work? The employer would like it if you need only half the time. It's not double the work. Share this post Link to post
Remy Lebeau 1394 Posted July 13, 2020 Just use Generics for this (see Testing the type of a generic in delphi), eg: type TSomeClass = class class procedure DoSomething<T>(const AParam: T); end; class procedure TSomeClass.DoSomething<T>(const AParam: T); begin case GetTypeKind(T) of tkUString: begin ShowMessage(PUnicodeString(@AParam)^); end; tkInteger: begin ShowMessage(IntToStr(PInteger(@AParam)^)); end; end; end; Share this post Link to post
Stefan Glienke 2002 Posted July 13, 2020 (edited) 6 minutes ago, Remy Lebeau said: Just use Generics for this Even worse than Variant - you can pass anything to DoSomething now and it simply wont get handled (or raise an exception if you add that as else case) Edited July 13, 2020 by Stefan Glienke 1 Share this post Link to post
PeterPanettone 157 Posted July 13, 2020 54 minutes ago, Remy Lebeau said: Just use Generics for this (see Testing the type of a generic in delphi), eg: type TSomeClass = class class procedure DoSomething<T>(const AParam: T); end; class procedure TSomeClass.DoSomething<T>(const AParam: T); begin case GetTypeKind(T) of tkUString: begin ShowMessage(PUnicodeString(@AParam)^); end; tkInteger: begin ShowMessage(IntToStr(PInteger(@AParam)^)); end; end; end; Nice professional code, compliments! But why making something so complicated and prone to errors, if it could be SO EASY with Open Type Arrays? Share this post Link to post
Vincent Parrett 750 Posted July 13, 2020 This all seems like an over complication for little gain. Perhaps the OP could explain better what he is trying to achieve by this? As @David Heffernan said, this is worse than method overloading, and the need for that could be removed in many cases by allowing named parameter passing. IMHO, most uses of GetTypeKind in generic code is just making up for the lack of constraint types on delphi's generics. 4 Share this post Link to post
Mahdi Safsafi 225 Posted July 13, 2020 @Stefan Glienke IsType<x> does not distinguish between distinct-type. And it could be a source of error. Using Overloaded function in some how can distinct at least if you specify the type explicitly: type TMyString = type string; procedure DoSomething2(const AParam: Integer); overload; begin Writeln('this is a integer :', AParam); end; procedure DoSomething2(const AParam: string); overload; begin Writeln('this is a string :', AParam); end; procedure DoSomething2(const AParam: TMyString); overload; begin Writeln('this is a TMyString :', AParam); end; var a: Integer; s: string; m: TMyString; begin s := 'string'; m := 'mystring'; DoSomething(a); DoSomething(s); DoSomething(m); DoSomething2(a); DoSomething2(s); DoSomething2(m); Readln; end. Share this post Link to post
PeterPanettone 157 Posted July 13, 2020 18 minutes ago, Mahdi Safsafi said: @Stefan Glienke IsType<x> does not distinguish between distinct-type. And it could be a source of error. Using Overloaded function in some how can distinct at least if you specify the type explicitly: type TMyString = type string; procedure DoSomething2(const AParam: Integer); overload; begin Writeln('this is a integer :', AParam); end; procedure DoSomething2(const AParam: string); overload; begin Writeln('this is a string :', AParam); end; procedure DoSomething2(const AParam: TMyString); overload; begin Writeln('this is a TMyString :', AParam); end; var a: Integer; s: string; m: TMyString; begin s := 'string'; m := 'mystring'; DoSomething(a); DoSomething(s); DoSomething(m); DoSomething2(a); DoSomething2(s); DoSomething2(m); Readln; end. Really nice code! However, your employer has to pay for the time you need to write overloaded code. (Though I assume you're a fast writer). Share this post Link to post
Cristian Peța 103 Posted July 14, 2020 (edited) 6 hours ago, PeterPanettone said: However, your employer has to pay for the time you need to write overloaded code. (Though I assume you're a fast writer). Depends. I added only 15K LOC last year to my project. A lot of time I spend thinking how to add features asked by customers to actual app logic. This doesn't have to do with old code but to old app logic and not to break old behavior. An to leave space for feature improvements because this is a 20 years old project. Edited July 14, 2020 by Cristian Peța 3 Share this post Link to post
Edwin Yip 154 Posted July 14, 2020 (edited) As @Stefan Glienke has pointed out, we have TVarRec, Variant and TValue to deal with situation of: The type of the parameter is uncertain But in addition to the above situation, open array parameters are designed to deal with even more complex situation: Both the types and number of parameters are uncertain. The most well-known case is the Format function. However, I think the OP is heading to a wrong direction to use open array parameters for simplifying the example code he's given. There is at least one better solution: procedure DoSomething(const AParam: string); overload; begin // procedure body here end; procedure DoSomething(const AParam: Integer); overload; begin DoSomething(IntToStr(aParam)); end; The overloaded procedures eliminate the use of the `if` statement and make the code simpler and easier to read. Just my 2 cents. Edit 1: Please note, 'less lines of code' <> simple, 'more lines of code' <> complexity Edit 2: One more benefit of my above alternative method - More user friendly to the clients (that uses the DoSomething procedures). Because the clients can see exactly the type of the parameters to provide, and this is exactly the advantage of statically typed languages over dynamically typed languages such as JS . Edited July 14, 2020 by Edwin Yip 5 Share this post Link to post
Lars Fosdal 1792 Posted July 14, 2020 I am trying to see a proper use case for this, as the example is too simplistic to make sense. What is the problem that needs to be solved? What is the duplicate work that needs to be eliminated? 1 Share this post Link to post
David Heffernan 2345 Posted July 14, 2020 8 hours ago, PeterPanettone said: However, your employer has to pay for the time you need to write overloaded code. (Though I assume you're a fast writer). In the OP you also write an implementation for each different type. So where is the saving? 4 Share this post Link to post
Stefan Glienke 2002 Posted July 14, 2020 10 hours ago, Mahdi Safsafi said: IsType<x> does not distinguish between distinct-type. And it could be a source of error. Using Overloaded function in some how can distinct at least if you specify the type explicitly: Then make it do a strict type check comparing with TValue.TypeInfo... also method overloading and distinct types is a source of errors in itself because they are always assignment compatible with their base type so that does not count as argument. I can also pass a TFileName, a TCaption or whatever to your DoSomething2 and it will happily go into the string overload. BTT if Delphi was more functional I could very well see more usecases for this in the form of monads like the maybe or either type because they can very well solve a lot of unnecessary cyclomatic complexity 1 Share this post Link to post
Guest Posted July 14, 2020 I wonder if there is trick(s) to make this works type TRecSI = record FInteger: Integer; FString: string; constructor Create(const s: string; i: Integer); end; constructor TRecSI.Create(const s: string; i: Integer); begin FInteger := i; FString := s; end; procedure DoSomething(var Anything); var T: TTypeKind; begin T := GetTypeKind(Anything); // ?????????? case T of tkInteger: Writeln(Integer(Anything)); /// end; end; var A: Integer; B: string; C: array of Byte; D: array of Integer; E: array of string; begin DoSomething(A); DoSomething(B); DoSomething(C); DoSomething(D); DoSomething(E); //DoSomething(TRecSI.Create('Hello',1234)); // or make this work Readln; end. Share this post Link to post