digitalmars.D - Value-based function overloading
- Nod (81/81) May 17 2005 I know a good idea when I see one, and I believe this topic is good enou...
- G.Vidal (60/60) May 18 2005 I like it.
- Matthias Becker (3/19) May 18 2005 Well if the only deonstructable operation is :: pattern matching isn't a...
- G.Vidal (4/6) May 18 2005 Of course that was a mere illustration.
- Nod (4/62) May 18 2005 Hey, I like the [x..y[ inclusive/exclusive ranges.
- Thomas Kuehne (17/58) May 18 2005 -----BEGIN PGP SIGNED MESSAGE-----
- Hasan Aljudy (24/93) May 18 2005 hmm, I can suggest of the top of my head the following: assume initially...
- Thomas Kuehne (16/80) May 18 2005 -----BEGIN PGP SIGNED MESSAGE-----
- Derek Parnell (23/80) May 18 2005 real x;
- Thomas Kuehne (10/86) May 18 2005 -----BEGIN PGP SIGNED MESSAGE-----
- Derek Parnell (9/40) May 18 2005 Hey! Neither can ranges ;-)
- Thomas Kuehne (19/52) May 19 2005 -----BEGIN PGP SIGNED MESSAGE-----
- Uwe Salomon (9/36) May 18 2005 His sample could be already wrong. When comparing floating points on
- James Dunne (22/73) May 19 2005 I like your example, Thomas; it's clear to the programmer, however the e...
- Thomas Kuehne (20/89) May 19 2005 -----BEGIN PGP SIGNED MESSAGE-----
- James Dunne (20/109) May 19 2005 I agree. Perhaps escaping them with \ would alleviate the parser's comp...
- Thomas Kuehne (12/33) May 19 2005 -----BEGIN PGP SIGNED MESSAGE-----
- Matthias Becker (6/21) May 19 2005 :: would be an operator to create an array:
- G.Vidal (21/48) May 19 2005 In D, the '~' operator would be more appropriate to construct arrays.
- G.Vidal (6/66) May 19 2005 And we could also imagine something more general:
- Matthias Becker (27/32) May 19 2005 Do you use pattern matching only to deconstruct arrays (or lists in caml...
- Nod (3/26) May 19 2005 Yeah. Now it's all crystal clear. :)
- Hasan Aljudy (62/168) May 18 2005 I like the idea, although one downside is some runtime over head, but I
- Hasan Aljudy (3/207) May 18 2005 woops, my bad, didn't read the whole original post, the OP already
- Nod (3/8) May 18 2005 Not at all, I posted a *draft* of the technique. Opinions/ideas are high...
- Nod (20/82) May 18 2005 Correct, that is what I think too. And since one would not be able to or...
- Hasan Aljudy (39/69) May 18 2005 I see your point.
- Nod (17/64) May 18 2005 Hmm yes, I guess it's a call to be made based on design philosophy, and ...
- Kyle Furlong (2/108) May 18 2005 Hear, hear.
- Kyle Furlong (3/136) May 18 2005 One thing that just occured to me is, cant you acheive the same goal
- Nod (11/19) May 18 2005 No, asserts and invariants are only for verifying that the function/prog...
I know a good idea when I see one, and I believe this topic is good enough to warrant a proper discussion. I will present a short summary and a proposed integration into D. Yeah, I know it's a bit "up there"... Maybe for 3.0? :) /**************************************** * Summary: ***/ As the name suggests, value-based function overloading does function call resolution based on the *values* of the function parameters in addition to the type-based overloading already found in D. Potential benefits are: clearer, more concise code, eases refactoring, separates the special cases from main algorithm, overriding buggy libraries easy. Potential dangers are: compiler complexity, it can be misused to create unreadable code (but what can't?). For reference, these are interesting: Antti(first): http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23643 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23651 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23686 Kevin Bealer: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23745 Maybe not interesting, but explains some benefits: Me: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23732 /**************************************** * Proposed integration with D ***/ Syntax: retType funcName(paramType paramName in paramRange) { ... } Example: void foo(char ch in '\n') { ... } void foo(int i in 0..10) { ... } void foo(Bar b in null) { ... } void foo(char[] str in new Regex("/fooi/i")) { ... } Nothing complex, a new keyword, in, which tests for membership in something, and after that a list (array literal?) of values. The last one would require an operator like opIn or something overloaded. This is only a draft though. Implementation: If all data is literal, and ranges checkable at compile-time, the full overload resolution can happen at compile time. If not, the compiler needs generate the if-else[1] construct needed to select the correct function. Ambiguites are illegal. If one value range overlaps another, a compile-time error is generated. Example: void foo(int a in 0..10) { ... } void foo(int a in 5..15) { ... } // illegal, range 5..15 overlaps 0..10 Multiple parameters can resolve each other's ambiguites and as such, two overloads are only ambiguous if all parameters common between the two have ambiguities. Example: 1) void foo(int a in 0..10, char b in 'x') { ... } 2) void foo(int a in 5..15, char b in 'y') { ... } // legal 3) void foo(int a in 0..5, char b in 'y') { ... } // legal, see below 4) void foo(int a in 0..5, char b in 'x') { ... } // illegal, ambiguous to 1) Only two overloads are considered at any one time, which is why number three above is legal; while both a is ambiguous with 1), and b is ambiguous with 2), *both* are not ambiguous at the same time. If ranges are not comparable at compile time, such as when using objects with overloaded opCmp, the compiler must defer ambiguity checking until run-time. This only needs to be done in debug builds. It is not necessary for the overloaded functions to cover all possible value ranges. If a value does not fit any of the overloaded functions, either a compile-time error is emitted if data is compile-time checkable, or an OverloadExecption is thrown at run-time. Example: 1) void foo(int a in 1..4) { ... } 2) void foo(int a in 6..9) { ... } int main() { int x = 5; foo(2); // calls 1) foo(5); // complains at compile-time foo(x); // throws at run-time } /**************************************** * In closing... ***/ I think one can compare this technique with forward references. While one certainly can manage without it, heck we've done it for >10 years, it is/would be a great relief to have. So, what do you all think? -Nod- [1] For brevity, I will only mention if-else constructs, but when the value ranges allow it, a switch-case construct could be used, or some combination of the two.
May 17 2005
I like it. But I'd prefer a match() statement similar to CamL's one: void foo (int e) { match (e) { -1: dothing(); [-1..10[: // -1 included to ten excluded dothis(); break; [10..15]: zero included to fifteen included dothat(); break; default: do(); } } That would be transformed by the compiler into this, more uglier and difficult to understand: void foo (int e) { if (e == -1) dothing(); if ((e >= 0) && (e < 10)) { dothis(); goto end; // break } if ((e >= 10) && (10 <= 15)) { dothat(); goto end; // break; } do(); end: } We can also imagine this for strings regexps: match (string) { RegExp (stuff..) { blabla break; } RegExp (other stuff) { blibli; break; } default: baba... } But the _real power_ is for recursive array: This function count the number of elements > 0 in an array (recursivity is useless here, but it's just an illustration) int positive (int[] array) { int count (int[] a, int t) { match (array) { []: return t; // end of array e::next : // e = first element of a, next = array.ptr +1 if (e > 0) count (next, t+1); else count (next, t); } } return count (array, 0); }
May 18 2005
But I'd prefer a match() statement similar to CamL's one:[snip]But the _real power_ is for recursive array: This function count the number of elements > 0 in an array (recursivity is useless here, but it's just an illustration) int positive (int[] array) { int count (int[] a, int t) { match (array) { []: return t; // end of array e::next : // e = first element of a, next = array.ptr +1 if (e > 0) count (next, t+1); else count (next, t); } } return count (array, 0); }Well if the only deonstructable operation is :: pattern matching isn't as powerfull as Caml's (or better ML's) pattern matching by far.
May 18 2005
Le Wed, 18 May 2005 11:00:11 +0000, Matthias Becker a écrit :Well if the only deonstructable operation is :: pattern matching isn't as powerfull as Caml's (or better ML's) pattern matching by far.Of course that was a mere illustration. All the other operations should be implemented, but as I was talking to people who potentially don't know pattern matching... well anyway
May 18 2005
In article <pan.2005.05.18.09.22.26.707525 wanadoo.fr>, G.Vidal says...I like it. But I'd prefer a match() statement similar to CamL's one: void foo (int e) { match (e) { -1: dothing(); [-1..10[: // -1 included to ten excluded dothis(); break; [10..15]: zero included to fifteen included dothat(); break; default: do(); } } That would be transformed by the compiler into this, more uglier and difficult to understand: void foo (int e) { if (e == -1) dothing(); if ((e >= 0) && (e < 10)) { dothis(); goto end; // break } if ((e >= 10) && (10 <= 15)) { dothat(); goto end; // break; } do(); end: } We can also imagine this for strings regexps: match (string) { RegExp (stuff..) { blabla break; } RegExp (other stuff) { blibli; break; } default: baba... } But the _real power_ is for recursive array: This function count the number of elements > 0 in an array (recursivity is useless here, but it's just an illustration) int positive (int[] array) { int count (int[] a, int t) { match (array) { []: return t; // end of array e::next : // e = first element of a, next = array.ptr +1 if (e > 0) count (next, t+1); else count (next, t); } } return count (array, 0); }Hey, I like the [x..y[ inclusive/exclusive ranges. The match statement looks scary though :) -Nod-
May 18 2005
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Nod schrieb am Wed, 18 May 2005 19:57:36 +0000 (UTC):In article <pan.2005.05.18.09.22.26.707525 wanadoo.fr>, G.Vidal says...<snip>I like it. But I'd prefer a match() statement similar to CamL's one: void foo (int e) { match (e) { -1: dothing(); [-1..10[: // -1 included to ten excluded dothis(); break; [10..15]: zero included to fifteen included dothat(); break; default: do(); } } That would be transformed by the compiler into this, more uglier and difficult to understand: void foo (int e) { if (e == -1) dothing(); if ((e >= 0) && (e < 10)) { dothis(); goto end; // break } if ((e >= 10) && (10 <= 15)) { dothat(); goto end; // break; } do(); end: }Hey, I like the [x..y[ inclusive/exclusive ranges.<snip> That syntax is a nightmare. Try to write a parser that generates a simple token tree and compare the complexity with and without exclusive-range support. Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges? Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCi+Bs3w+/yD4P9tIRAnEHAKCY18ISk1OSixy7GT8+vnVeiYqHpgCfRWN/ SVSZOEy+YFDcAtm+ngavrGc= =/llO -----END PGP SIGNATURE-----
May 18 2005
Thomas Kuehne wrote:-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Nod schrieb am Wed, 18 May 2005 19:57:36 +0000 (UTC):hmm, I can suggest of the top of my head the following: assume initially that both ends are exclusive, unless these ends are explicitly used somewhere else by themselves foo( 1..5 ) //initially inclusive { .. } foo( 5 ) //the previous declaration becomes exclusive but this could easily lead to duplicate code nd/or redundancy.. foo( int i ) { //complex code } foo( 1..5 ) { //something else } foo( 5 ) { //this is not a special case, it's just for exclusion, so we //will probably copy-paste the complex code from the general //function, or call something like // default foo( 5 ); //but even if we do that, the redundancy is still there }In article <pan.2005.05.18.09.22.26.707525 wanadoo.fr>, G.Vidal says...<snip>I like it. But I'd prefer a match() statement similar to CamL's one: void foo (int e) { match (e) { -1: dothing(); [-1..10[: // -1 included to ten excluded dothis(); break; [10..15]: zero included to fifteen included dothat(); break; default: do(); } } That would be transformed by the compiler into this, more uglier and difficult to understand: void foo (int e) { if (e == -1) dothing(); if ((e >= 0) && (e < 10)) { dothis(); goto end; // break } if ((e >= 10) && (10 <= 15)) { dothat(); goto end; // break; } do(); end: }Hey, I like the [x..y[ inclusive/exclusive ranges.<snip> That syntax is a nightmare. Try to write a parser that generates a simple token tree and compare the complexity with and without exclusive-range support. Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges? Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCi+Bs3w+/yD4P9tIRAnEHAKCY18ISk1OSixy7GT8+vnVeiYqHpgCfRWN/ SVSZOEy+YFDcAtm+ngavrGc= =/llO -----END PGP SIGNATURE-----
May 18 2005
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Hasan Aljudy schrieb am Wed, 18 May 2005 17:18:20 -0600:Thomas Kuehne wrote:The borders should be inclusive, otherwise your run into problems with type.max/type.min.Nod schrieb am Wed, 18 May 2005 19:57:36 +0000 (UTC):hmm, I can suggest of the top of my head the following: assume initially that both ends are exclusive, unless these ends are explicitly used somewhere else by themselvesIn article <pan.2005.05.18.09.22.26.707525 wanadoo.fr>, G.Vidal says...<snip>I like it. But I'd prefer a match() statement similar to CamL's one: void foo (int e) { match (e) { -1: dothing(); [-1..10[: // -1 included to ten excluded dothis(); break; [10..15]: zero included to fifteen included dothat(); break; default: do(); } } That would be transformed by the compiler into this, more uglier and difficult to understand: void foo (int e) { if (e == -1) dothing(); if ((e >= 0) && (e < 10)) { dothis(); goto end; // break } if ((e >= 10) && (10 <= 15)) { dothat(); goto end; // break; } do(); end: }Hey, I like the [x..y[ inclusive/exclusive ranges.<snip> That syntax is a nightmare. Try to write a parser that generates a simple token tree and compare the complexity with and without exclusive-range support. Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?foo( 1..5 ) //initially inclusive { .. } foo( 5 ) //the previous declaration becomes exclusiveinclusive and without change of the border type foo(1..4) foo(5) Why do you need to the change the border evaluation in dependency of another declaration? Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCi/Ao3w+/yD4P9tIRAgzsAJ91TDsGM6ZwEfUjVUywtbvZEE3bOQCeJ28y 9YkhdMPGqpEXsxJowsgF06A= =rUPG -----END PGP SIGNATURE-----
May 18 2005
On Thu, 19 May 2005 02:40:16 +0200, Thomas Kuehne wrote:-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Nod schrieb am Wed, 18 May 2005 19:57:36 +0000 (UTC):real x; . . . switch (x) { case 0.0 ... 0.9999999999: . . . break; case 1.0 ... 1.9999999999: . . . } when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . } -- Derek Melbourne, Australia 19/05/2005 9:23:37 AMIn article <pan.2005.05.18.09.22.26.707525 wanadoo.fr>, G.Vidal says...<snip>I like it. But I'd prefer a match() statement similar to CamL's one: void foo (int e) { match (e) { -1: dothing(); [-1..10[: // -1 included to ten excluded dothis(); break; [10..15]: zero included to fifteen included dothat(); break; default: do(); } } That would be transformed by the compiler into this, more uglier and difficult to understand: void foo (int e) { if (e == -1) dothing(); if ((e >= 0) && (e < 10)) { dothis(); goto end; // break } if ((e >= 10) && (10 <= 15)) { dothat(); goto end; // break; } do(); end: }Hey, I like the [x..y[ inclusive/exclusive ranges.<snip> That syntax is a nightmare. Try to write a parser that generates a simple token tree and compare the complexity with and without exclusive-range support. Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?
May 18 2005
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Derek Parnell schrieb am Thu, 19 May 2005 09:25:53 +1000:On Thu, 19 May 2005 02:40:16 +0200, Thomas Kuehne wrote:Floating types can't be used as switch conditionals. Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCi+3O3w+/yD4P9tIRAnmNAKDPDpwm8hXOR5yw6wpJqH13j3S4MACgqCsI hzLEYiHYbxY28Wb4Lu6B0Jg= =7X2i -----END PGP SIGNATURE----------BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Nod schrieb am Wed, 18 May 2005 19:57:36 +0000 (UTC):real x; . . . switch (x) { case 0.0 ... 0.9999999999: . . . break; case 1.0 ... 1.9999999999: . . . } when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . }In article <pan.2005.05.18.09.22.26.707525 wanadoo.fr>, G.Vidal says...<snip>I like it. But I'd prefer a match() statement similar to CamL's one: void foo (int e) { match (e) { -1: dothing(); [-1..10[: // -1 included to ten excluded dothis(); break; [10..15]: zero included to fifteen included dothat(); break; default: do(); } } That would be transformed by the compiler into this, more uglier and difficult to understand: void foo (int e) { if (e == -1) dothing(); if ((e >= 0) && (e < 10)) { dothis(); goto end; // break } if ((e >= 10) && (10 <= 15)) { dothat(); goto end; // break; } do(); end: }Hey, I like the [x..y[ inclusive/exclusive ranges.<snip> That syntax is a nightmare. Try to write a parser that generates a simple token tree and compare the complexity with and without exclusive-range support. Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?
May 18 2005
On Thu, 19 May 2005 03:37:18 +0200, Thomas Kuehne wrote:-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Derek Parnell schrieb am Thu, 19 May 2005 09:25:53 +1000:[snip]Hey! Neither can ranges ;-) We are brainstorming here, right? If we can consider ranges then I think we can consider switches using any type. -- Derek Melbourne, Australia 19/05/2005 10:25:43 AMFloating types can't be used as switch conditionals.Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?real x; . . . switch (x) { case 0.0 ... 0.9999999999: . . . break; case 1.0 ... 1.9999999999: . . . } when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . }
May 18 2005
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Derek Parnell schrieb am Thu, 19 May 2005 10:28:08 +1000:On Thu, 19 May 2005 03:37:18 +0200, Thomas Kuehne wrote:The problem with floating types are their rounding problems. Rewrite of your sample in inclusive ranges. real x; ... switch(x){ case 0.0 ... 1-real.epsilon: .... case 1.0 ... 2-real.epsilon: .... } Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCjENn3w+/yD4P9tIRAhT9AKCMEN/pAS8xH57tHllWHpvh1GGL5QCdG2Sf Sx0XzxFMvxHe8oGzAX6gQmE= =Jo3W -----END PGP SIGNATURE-----Derek Parnell schrieb am Thu, 19 May 2005 09:25:53 +1000:[snip]Hey! Neither can ranges ;-) We are brainstorming here, right? If we can consider ranges then I think we can consider switches using any type.Floating types can't be used as switch conditionals.Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?real x; . . . switch (x) { case 0.0 ... 0.9999999999: . . . break; case 1.0 ... 1.9999999999: . . . } when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . }
May 19 2005
His sample could be already wrong. When comparing floating points on equality, you mostly mean something like this: if (x >= (0.0 - delta) && x < (1.0 - delta)) where delta will be greater than epsilon often. This is certainly a problem when dealing with floating points... i remember a program where this issue cost me almost a day for some nested if clauses (i needed some time to figure out where to add/subtract the delta). Ciao uweThe problem with floating types are their rounding problems. Rewrite of your sample in inclusive ranges. real x; ... switch(x){ case 0.0 ... 1-real.epsilon: .... case 1.0 ... 2-real.epsilon: .... }Hey! Neither can ranges ;-) We are brainstorming here, right? If we can consider ranges then I think we can consider switches using any type.when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . }Floating types can't be used as switch conditionals.
May 18 2005
In article <d8ogr7.3b2.thomas-dloop laermschleuder.kuehne.cn>, Thomas Kuehne says...-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Derek Parnell schrieb am Thu, 19 May 2005 10:28:08 +1000:I like your example, Thomas; it's clear to the programmer, however the epsilon is specific to whichever floating point type you're finding ranges of. real's epsilon won't work for double and float, and I'm sure you were aware of this. I'm also sure you meant it as a simple example demonstrating the technique. But it got me thinking... A syntax of inclusive and exclusive bounds should be proposed to fix the range problems of floating point types that lets the compiler choose the correct epsilon - or even better - generate simple floating point compare operations for range checking. Basing my initial proposal on mathematical range syntax: case (1.0 .. 2.0): // x > 1.0 && x < 2.0 case [1.0 .. 2.0): // x >= 1.0 && x < 2.0 case (1.0 .. 2.0]: // x > 1.0 && x <= 2.0 case [1.0 .. 2.0]: // x >= 1.0 && x <= 2.0 I'm sure the compiler can detect range conflicts with this inclusive/exclusive range syntax with a few simple heuristics or rules. NB: I might have the meaning of () and [] reversed here, but I'm pretty sure this is mathematically correct. Regards, James DunneOn Thu, 19 May 2005 03:37:18 +0200, Thomas Kuehne wrote:The problem with floating types are their rounding problems. Rewrite of your sample in inclusive ranges. real x; ... switch(x){ case 0.0 ... 1-real.epsilon: .... case 1.0 ... 2-real.epsilon: .... } ThomasDerek Parnell schrieb am Thu, 19 May 2005 09:25:53 +1000:[snip]Hey! Neither can ranges ;-) We are brainstorming here, right? If we can consider ranges then I think we can consider switches using any type.Floating types can't be used as switch conditionals.Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?real x; . . . switch (x) { case 0.0 ... 0.9999999999: . . . break; case 1.0 ... 1.9999999999: . . . } when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . }
May 19 2005
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 James Dunne schrieb am Thu, 19 May 2005 09:33:02 +0000 (UTC):In article <d8ogr7.3b2.thomas-dloop laermschleuder.kuehne.cn>, Thomas Kuehne says...Any syntax that uses un-paired braces raises huge complexity issues for any code parser. switch(x){ case 0.0 ... 1-typeof(x).epsilon: .... case 1.0 ... 2-typeof(x).epsilon: .... } missing: int/byte/short/long/char/dchar/wchar.epsilon==1 A shortcut(e.g. $E) for typeof(x).epsilon in case statements would be nice. Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCjIIK3w+/yD4P9tIRAvG3AJ9J/fD+76VcCYF/vKMM/H9qgty8MACgjDO6 o9S4/HdozZmpQh4zAkEv/PI= =rPfq -----END PGP SIGNATURE-----Derek Parnell schrieb am Thu, 19 May 2005 10:28:08 +1000:I like your example, Thomas; it's clear to the programmer, however the epsilon is specific to whichever floating point type you're finding ranges of. real's epsilon won't work for double and float, and I'm sure you were aware of this. I'm also sure you meant it as a simple example demonstrating the technique. But it got me thinking... A syntax of inclusive and exclusive bounds should be proposed to fix the range problems of floating point types that lets the compiler choose the correct epsilon - or even better - generate simple floating point compare operations for range checking. Basing my initial proposal on mathematical range syntax: case (1.0 .. 2.0): // x > 1.0 && x < 2.0 case [1.0 .. 2.0): // x >= 1.0 && x < 2.0 case (1.0 .. 2.0]: // x > 1.0 && x <= 2.0 case [1.0 .. 2.0]: // x >= 1.0 && x <= 2.0 I'm sure the compiler can detect range conflicts with this inclusive/exclusive range syntax with a few simple heuristics or rules.On Thu, 19 May 2005 03:37:18 +0200, Thomas Kuehne wrote:The problem with floating types are their rounding problems. Rewrite of your sample in inclusive ranges. real x; ... switch(x){ case 0.0 ... 1-real.epsilon: .... case 1.0 ... 2-real.epsilon: .... }Derek Parnell schrieb am Thu, 19 May 2005 09:25:53 +1000:[snip]Hey! Neither can ranges ;-) We are brainstorming here, right? If we can consider ranges then I think we can consider switches using any type.Floating types can't be used as switch conditionals.Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?real x; . . . switch (x) { case 0.0 ... 0.9999999999: . . . break; case 1.0 ... 1.9999999999: . . . } when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . }
May 19 2005
In article <d8p0gb.3k5.thomas-dloop laermschleuder.kuehne.cn>, Thomas Kuehne says...-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 James Dunne schrieb am Thu, 19 May 2005 09:33:02 +0000 (UTC):I agree. Perhaps escaping them with \ would alleviate the parser's complexity issues with unmatched braces? I don't think there is a \ token in D, so creating \[, \(, \], \) tokens would be trivial for this task. Would that really look that bad? It'd be easy to parse: case \(1.0 .. 2.0\): // x > 1.0 && x < 2.0 case \[1.0 .. 2.0\): // x >= 1.0 && x < 2.0 case \(1.0 .. 2.0\]: // x > 1.0 && x <= 2.0 case \[1.0 .. 2.0\]: // x >= 1.0 && x <= 2.0 Any other character not used in D token parsing would be an equally likely the single-character realm. There'd also have to be the default range where both boundaries are inclusive when you don't specify explicit range syntax.In article <d8ogr7.3b2.thomas-dloop laermschleuder.kuehne.cn>, Thomas Kuehne says...Any syntax that uses un-paired braces raises huge complexity issues for any code parser.Derek Parnell schrieb am Thu, 19 May 2005 10:28:08 +1000:I like your example, Thomas; it's clear to the programmer, however the epsilon is specific to whichever floating point type you're finding ranges of. real's epsilon won't work for double and float, and I'm sure you were aware of this. I'm also sure you meant it as a simple example demonstrating the technique. But it got me thinking... A syntax of inclusive and exclusive bounds should be proposed to fix the range problems of floating point types that lets the compiler choose the correct epsilon - or even better - generate simple floating point compare operations for range checking. Basing my initial proposal on mathematical range syntax: case (1.0 .. 2.0): // x > 1.0 && x < 2.0 case [1.0 .. 2.0): // x >= 1.0 && x < 2.0 case (1.0 .. 2.0]: // x > 1.0 && x <= 2.0 case [1.0 .. 2.0]: // x >= 1.0 && x <= 2.0 I'm sure the compiler can detect range conflicts with this inclusive/exclusive range syntax with a few simple heuristics or rules.On Thu, 19 May 2005 03:37:18 +0200, Thomas Kuehne wrote:The problem with floating types are their rounding problems. Rewrite of your sample in inclusive ranges. real x; ... switch(x){ case 0.0 ... 1-real.epsilon: .... case 1.0 ... 2-real.epsilon: .... }Derek Parnell schrieb am Thu, 19 May 2005 09:25:53 +1000:[snip]Hey! Neither can ranges ;-) We are brainstorming here, right? If we can consider ranges then I think we can consider switches using any type.Floating types can't be used as switch conditionals.Is there any situation where exclusive ranges are neccesary and can't be replaced by inclusive ranges?real x; . . . switch (x) { case 0.0 ... 0.9999999999: . . . break; case 1.0 ... 1.9999999999: . . . } when what I really need is ... if (x >= 0.0 and < 1.0) { . . . } else if (x >= 1.0 and x < 2.0) { . . . }switch(x){ case 0.0 ... 1-typeof(x).epsilon: .... case 1.0 ... 2-typeof(x).epsilon: .... } missing: int/byte/short/long/char/dchar/wchar.epsilon==1 A shortcut(e.g. $E) for typeof(x).epsilon in case statements would be nice. ThomasI'm still a bit skeptical on the epsilon idea. That $E would have to be aware of your (x) type in typeof(x).espilon. Perhaps $E(float), $E(double) would work for what you're proposing? Regards, James Dunne
May 19 2005
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 James Dunne schrieb am Thu, 19 May 2005 10:41:11 +0000 (UTC):In article <d8p0gb.3k5.thomas-dloop laermschleuder.kuehne.cn>, Thomas Kuehne says...<snip>Any other character not used in D token parsing would be an equally likely the single-character realm.<snip>$E knows the type due to "switch(x)". -----BEGIN PGP SIGNATURE----- iD8DBQFCjI3q3w+/yD4P9tIRAnO7AKCDXQEVIDy7FuBLhoeL21M4txXgcQCfY9Oe D+/BvtSR/+N0GtypcfE6HIY= =vCb+ -----END PGP SIGNATURE-----switch(x){ case 0.0 ... 1-typeof(x).epsilon: .... case 1.0 ... 2-typeof(x).epsilon: .... } missing: int/byte/short/long/char/dchar/wchar.epsilon==1 A shortcut(e.g. $E) for typeof(x).epsilon in case statements would be nice.I'm still a bit skeptical on the epsilon idea. That $E would have to be aware of your (x) type in typeof(x).espilon. Perhaps $E(float), $E(double) would work for what you're proposing?
May 19 2005
:: would be an operator to create an array: 1 :: [5, 9, 42] would create the array [1, 5, 9, 42]. This operator would have a backward-mode to deconstruct an array into it's first array and the other elements. This is used in this match statement here. In ML :: is a constructor to construct lists. Each constructor can be used to deconstruct an object.int positive (int[] array) { int count (int[] a, int t) { match (array) { []: return t; // end of array e::next : // e = first element of a, next = array.ptr +1 if (e > 0) count (next, t+1); else count (next, t); } } return count (array, 0); }The match statement looks scary though :)
May 19 2005
In D, the '~' operator would be more appropriate to construct arrays. ( Is this allowed by the way: int[] e = a ~ [1,2,3]; with a beeing int or int[] ? ) The '::' should be reserved for deconstruction only match (a) { foo::bar : // here, put code to be executed if a is not an // empty array, foo would then be a variable with // the first element of the array a, and bar // would be the array a[1..$] (with its first // element removed) break: // optional [1,2,3]::bar : // here put code to be executed if a begins with // the subarray [1,2,3], then bar would be // the array a[3..$] (with the subarray removed) [] : // here put code if a is an "empty" array. } I like pattern matching. This looks OK don't you think? the equivalent with if, else, == would be more scary. What do you think, feedback please ! See ya, gracias Le Thu, 19 May 2005 10:12:02 +0000, Matthias Becker a écrit ::: would be an operator to create an array: 1 :: [5, 9, 42] would create the array [1, 5, 9, 42]. This operator would have a backward-mode to deconstruct an array into it's first array and the other elements. This is used in this match statement here. In ML :: is a constructor to construct lists. Each constructor can be used to deconstruct an object.int positive (int[] array) { int count (int[] a, int t) { match (array) { []: return t; // end of array e::next : // e = first element of a, next = array.ptr +1 if (e > 0) count (next, t+1); else count (next, t); } } return count (array, 0); }The match statement looks scary though :)
May 19 2005
And we could also imagine something more general: a::b::[3,4]::next Match would occur if the 3th and 4th element of a would be '3' and '4' a and b would contains the first and the second values, next would be a[4..$] Etc..... Le Thu, 19 May 2005 13:01:55 +0200, G.Vidal a écrit :In D, the '~' operator would be more appropriate to construct arrays. ( Is this allowed by the way: int[] e = a ~ [1,2,3]; with a beeing int or int[] ? ) The '::' should be reserved for deconstruction only match (a) { foo::bar : // here, put code to be executed if a is not an // empty array, foo would then be a variable with // the first element of the array a, and bar // would be the array a[1..$] (with its first // element removed) break: // optional [1,2,3]::bar : // here put code to be executed if a begins with // the subarray [1,2,3], then bar would be // the array a[3..$] (with the subarray removed) [] : // here put code if a is an "empty" array.I like pattern matching. This looks OK don't you think? the equivalent with if, else, == would be more scary. What do you think, feedback please ! See ya, gracias Le Thu, 19 May 2005 10:12:02 +0000, Matthias Becker a écrit ::: would be an operator to create an array: 1 :: [5, 9, 42] would create the array [1, 5, 9, 42]. This operator would have a backward-mode to deconstruct an array into it's first array and the other elements. This is used in this match statement here. In ML :: is a constructor to construct lists. Each constructor can be used to deconstruct an object.int positive (int[] array) { int count (int[] a, int t) { match (array) { []: return t; // end of array e::next : // e = first element of a, next = array.ptr +1 if (e > 0) count (next, t+1); else count (next, t); } } return count (array, 0); }The match statement looks scary though :)
May 19 2005
And we could also imagine something more general: a::b::[3,4]::next Match would occur if the 3th and 4th element of a would be '3' and '4' a and b would contains the first and the second values, next would be a[4..$] Etc.....Do you use pattern matching only to deconstruct arrays (or lists in caml)? Normaly you can use it for anything and that's why pattern matching is interesting. Some SML: Or simpler other things like a binaray tree with related functions ..
May 19 2005
In article <d6hopi$1geo$1 digitaldaemon.com>, Matthias Becker says...Yeah. Now it's all crystal clear. :) -Nod-:: would be an operator to create an array: 1 :: [5, 9, 42] would create the array [1, 5, 9, 42]. This operator would have a backward-mode to deconstruct an array into it's first array and the other elements. This is used in this match statement here. In ML :: is a constructor to construct lists. Each constructor can be used to deconstruct an object.int positive (int[] array) { int count (int[] a, int t) { match (array) { []: return t; // end of array e::next : // e = first element of a, next = array.ptr +1 if (e > 0) count (next, t+1); else count (next, t); } } return count (array, 0); }The match statement looks scary though :)
May 19 2005
Nod wrote:I know a good idea when I see one, and I believe this topic is good enough to warrant a proper discussion. I will present a short summary and a proposed integration into D. Yeah, I know it's a bit "up there"... Maybe for 3.0? :) /**************************************** * Summary: ***/ As the name suggests, value-based function overloading does function call resolution based on the *values* of the function parameters in addition to the type-based overloading already found in D. Potential benefits are: clearer, more concise code, eases refactoring, separates the special cases from main algorithm, overriding buggy libraries easy. Potential dangers are: compiler complexity, it can be misused to create unreadable code (but what can't?). For reference, these are interesting: Antti(first): http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23643 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23651 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23686 Kevin Bealer: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23745 Maybe not interesting, but explains some benefits: Me: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23732 /**************************************** * Proposed integration with D ***/ Syntax: retType funcName(paramType paramName in paramRange) { ... } Example: void foo(char ch in '\n') { ... } void foo(int i in 0..10) { ... } void foo(Bar b in null) { ... } void foo(char[] str in new Regex("/fooi/i")) { ... } Nothing complex, a new keyword, in, which tests for membership in something, and after that a list (array literal?) of values. The last one would require an operator like opIn or something overloaded. This is only a draft though. Implementation: If all data is literal, and ranges checkable at compile-time, the full overload resolution can happen at compile time. If not, the compiler needs generate the if-else[1] construct needed to select the correct function. Ambiguites are illegal. If one value range overlaps another, a compile-time error is generated. Example: void foo(int a in 0..10) { ... } void foo(int a in 5..15) { ... } // illegal, range 5..15 overlaps 0..10 Multiple parameters can resolve each other's ambiguites and as such, two overloads are only ambiguous if all parameters common between the two have ambiguities. Example: 1) void foo(int a in 0..10, char b in 'x') { ... } 2) void foo(int a in 5..15, char b in 'y') { ... } // legal 3) void foo(int a in 0..5, char b in 'y') { ... } // legal, see below 4) void foo(int a in 0..5, char b in 'x') { ... } // illegal, ambiguous to 1) Only two overloads are considered at any one time, which is why number three above is legal; while both a is ambiguous with 1), and b is ambiguous with 2), *both* are not ambiguous at the same time. If ranges are not comparable at compile time, such as when using objects with overloaded opCmp, the compiler must defer ambiguity checking until run-time. This only needs to be done in debug builds. It is not necessary for the overloaded functions to cover all possible value ranges. If a value does not fit any of the overloaded functions, either a compile-time error is emitted if data is compile-time checkable, or an OverloadExecption is thrown at run-time. Example: 1) void foo(int a in 1..4) { ... } 2) void foo(int a in 6..9) { ... } int main() { int x = 5; foo(2); // calls 1) foo(5); // complains at compile-time foo(x); // throws at run-time } /**************************************** * In closing... ***/ I think one can compare this technique with forward references. While one certainly can manage without it, heck we've done it for >10 years, it is/would be a great relief to have. So, what do you all think? -Nod- [1] For brevity, I will only mention if-else constructs, but when the value ranges allow it, a switch-case construct could be used, or some combination of the two.I like the idea, although one downside is some runtime over head, but I think it's not really an issue, it would probably be equivilent to just an if statement. Maybe there should be some keyword that must be used when declaring the function and/or using it, so that who ever reads the code can see that there are other parts of the function somewhere else. I say other "parts", because this is not really function overloading, it's just seperating the function's parts into seperate functions. so that function(int i) { if( i == 0 ) { .... } else if( i >= 1 && i <= 5 ) { .... } else { .... } } becomes function( int 0 ) { .... } function( int 1..5 ) { .... } function( int i ) { .... } This might be confusing for some, so there probably must be some rules to make it clearer for the reader (since the original motivation for the idea is clarity and maintainability), at least all the function parts must be in the same module. Also, maybe the part functions should be nested inside the whole function? and special cases should be above the general case, because they are handled logically as an if..else? So it maybe a clearer structur than a switch /keyword/ function(int i) { function( 0 ) { .... } function( 1..5 ) { .... } function( i ) { .... } } And the function may contain nothing except for the "parts"
May 18 2005
Hasan Aljudy wrote:Nod wrote:woops, my bad, didn't read the whole original post, the OP already outlined the specs for the feature.I know a good idea when I see one, and I believe this topic is good enough to warrant a proper discussion. I will present a short summary and a proposed integration into D. Yeah, I know it's a bit "up there"... Maybe for 3.0? :) /**************************************** * Summary: ***/ As the name suggests, value-based function overloading does function call resolution based on the *values* of the function parameters in addition to the type-based overloading already found in D. Potential benefits are: clearer, more concise code, eases refactoring, separates the special cases from main algorithm, overriding buggy libraries easy. Potential dangers are: compiler complexity, it can be misused to create unreadable code (but what can't?). For reference, these are interesting: Antti(first): http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23643 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23651 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23686 Kevin Bealer: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23745 Maybe not interesting, but explains some benefits: Me: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23732 /**************************************** * Proposed integration with D ***/ Syntax: retType funcName(paramType paramName in paramRange) { ... } Example: void foo(char ch in '\n') { ... } void foo(int i in 0..10) { ... } void foo(Bar b in null) { ... } void foo(char[] str in new Regex("/fooi/i")) { ... } Nothing complex, a new keyword, in, which tests for membership in something, and after that a list (array literal?) of values. The last one would require an operator like opIn or something overloaded. This is only a draft though. Implementation: If all data is literal, and ranges checkable at compile-time, the full overload resolution can happen at compile time. If not, the compiler needs generate the if-else[1] construct needed to select the correct function. Ambiguites are illegal. If one value range overlaps another, a compile-time error is generated. Example: void foo(int a in 0..10) { ... } void foo(int a in 5..15) { ... } // illegal, range 5..15 overlaps 0..10 Multiple parameters can resolve each other's ambiguites and as such, two overloads are only ambiguous if all parameters common between the two have ambiguities. Example: 1) void foo(int a in 0..10, char b in 'x') { ... } 2) void foo(int a in 5..15, char b in 'y') { ... } // legal 3) void foo(int a in 0..5, char b in 'y') { ... } // legal, see below 4) void foo(int a in 0..5, char b in 'x') { ... } // illegal, ambiguous to 1) Only two overloads are considered at any one time, which is why number three above is legal; while both a is ambiguous with 1), and b is ambiguous with 2), *both* are not ambiguous at the same time. If ranges are not comparable at compile time, such as when using objects with overloaded opCmp, the compiler must defer ambiguity checking until run-time. This only needs to be done in debug builds. It is not necessary for the overloaded functions to cover all possible value ranges. If a value does not fit any of the overloaded functions, either a compile-time error is emitted if data is compile-time checkable, or an OverloadExecption is thrown at run-time. Example: 1) void foo(int a in 1..4) { ... } 2) void foo(int a in 6..9) { ... } int main() { int x = 5; foo(2); // calls 1) foo(5); // complains at compile-time foo(x); // throws at run-time } /**************************************** * In closing... ***/ I think one can compare this technique with forward references. While one certainly can manage without it, heck we've done it for >10 years, it is/would be a great relief to have. So, what do you all think? -Nod- [1] For brevity, I will only mention if-else constructs, but when the value ranges allow it, a switch-case construct could be used, or some combination of the two.I like the idea, although one downside is some runtime over head, but I think it's not really an issue, it would probably be equivilent to just an if statement. Maybe there should be some keyword that must be used when declaring the function and/or using it, so that who ever reads the code can see that there are other parts of the function somewhere else. I say other "parts", because this is not really function overloading, it's just seperating the function's parts into seperate functions. so that function(int i) { if( i == 0 ) { .... } else if( i >= 1 && i <= 5 ) { .... } else { .... } } becomes function( int 0 ) { .... } function( int 1..5 ) { .... } function( int i ) { .... } This might be confusing for some, so there probably must be some rules to make it clearer for the reader (since the original motivation for the idea is clarity and maintainability), at least all the function parts must be in the same module. Also, maybe the part functions should be nested inside the whole function? and special cases should be above the general case, because they are handled logically as an if..else? So it maybe a clearer structur than a switch /keyword/ function(int i) { function( 0 ) { .... } function( 1..5 ) { .... } function( i ) { .... } } And the function may contain nothing except for the "parts"
May 18 2005
In article <d6fkhg$2i61$2 digitaldaemon.com>, Hasan Aljudy says...Not at all, I posted a *draft* of the technique. Opinions/ideas are highly welcome!<snipwoops, my bad, didn't read the whole original post, the OP already outlined the specs for the feature.
May 18 2005
In article <d6fkde$2i61$1 digitaldaemon.com>, Hasan Aljudy says...Correct, that is what I think too. And since one would not be able to order the if/else statement after frequency, codegen may be slightly less optimized than a hand-coded one. But speed is rarely *that* important anyways. The gained clarity will make up for it.<snip>I like the idea, although one downside is some runtime over head, but I think it's not really an issue, it would probably be equivilent to just an if statement.Maybe there should be some keyword that must be used when declaring the function and/or using it, so that who ever reads the code can see that there are other parts of the function somewhere else. I say other "parts", because this is not really function overloading, it's just seperating the function's parts into seperate functions. so that function(int i) { if( i == 0 ) { .... } else if( i >= 1 && i <= 5 ) { .... } else { .... } } becomes function( int 0 ) { .... } function( int 1..5 ) { .... } function( int i ) { .... } This might be confusing for some, so there probably must be some rules to make it clearer for the reader (since the original motivation for the idea is clarity and maintainability), at least all the function parts must be in the same module.Yes, this may be confusing. However, I believe that putting too many restrictions on the technique will also limit its value. In this way, you would not be able to overload any function to which you do not have write access to the source. While that wouldn't be a very clear thing to do, sometimes you really need to, and when you do, you would have to use an even worse kludge like a wrapper function.Also, maybe the part functions should be nested inside the whole function? and special cases should be above the general case, because they are handled logically as an if..else? So it maybe a clearer structur than a switch /keyword/ function(int i) { function( 0 ) { .... } function( 1..5 ) { .... } function( i ) { .... } } And the function may contain nothing except for the "parts"This is again an interesting idea, and you are right, it would indeed make things very clear. But this would also be somewhat limiting. For instance, when splitting a function, that is turning it into two value-overloaded ones, you would have to add both the outer function and a new inner. This is not far from the original case of adding to an if/else statement *and* adding a new function. Too much editing imho. Don't get me wrong though, there *will* have to be limits to the technique, I just don't think boxing it into keyword/function/module "jails" is giving enough freedom for the programmer. But this may just be me being too liberal :)
May 18 2005
Nod wrote:In article <d6fkde$2i61$1 digitaldaemon.com>, Hasan Aljudy says...I see your point. I still think the general function should have a keyword associated with it to indicate that it has value-based overloaders (if that's what you'd call it), otherwise it can cause serious ocnfusion and headechs when debugging, where you look at a function, expect certain results, but see that the actual results are totally different than what you expected. Then you're not so sure which values are overloaded and which are not, and unless you have a really good IDE, then you're gonna spend (read: waste) some time trying to figure out whether a specific value is overloaded or not. So I think some rules must be set to elliminate such problems. You can easily override library functions like this: file1.d ---------------------- import library; //we will override "foo" in the library void foo( 0 ) { //do something special .. } void foo( int i ) { library.foo( i ); } -------------------- file2.d ------------------- //don't import the library, import your overloads import file1; foo(0); //value overloaded foo(4); //default library function ------------------- Anyway, I think in most cases users aren't exactly aware of the details of library implementations, so I dunno, but I don't think that case would come up very often. However, the case of debugging code and not being sure which values are overloaded and which are not is very very likely to come up, infact I think it comes up all the time.Yes, this may be confusing. However, I believe that putting too many restrictions on the technique will also limit its value. In this way, you would not be able to overload any function to which you do not have write access to the source. While that wouldn't be a very clear thing to do, sometimes you really need to, and when you do, you would have to use an even worse kludge like a wrapper function.<snip><snip><snip>This is again an interesting idea, and you are right, it would indeed make things very clear. But this would also be somewhat limiting. For instance, when splitting a function, that is turning it into two value-overloaded ones, you would have to add both the outer function and a new inner. This is not far from the original case of adding to an if/else statement *and* adding a new function. Too much editing imho. Don't get me wrong though, there *will* have to be limits to the technique, I just don't think boxing it into keyword/function/module "jails" is giving enough freedom for the programmer. But this may just be me being too liberal :)
May 18 2005
In article <d6ghoo$for$1 digitaldaemon.com>, Hasan Aljudy says...Nod wrote:That's the working name. :)In article <d6fkde$2i61$1 digitaldaemon.com>, Hasan Aljudy says...I see your point. I still think the general function should have a keyword associated with it to indicate that it has value-based overloaders (if that's what you'd call it),<snip><snip><snip>otherwise it can cause serious ocnfusion and headechs when debugging, where you look at a function, expect certain results, but see that the actual results are totally different than what you expected. Then you're not so sure which values are overloaded and which are not, and unless you have a really good IDE, then you're gonna spend (read: waste) some time trying to figure out whether a specific value is overloaded or not. So I think some rules must be set to elliminate such problems. You can easily override library functions like this: file1.d ---------------------- import library; //we will override "foo" in the library void foo( 0 ) { //do something special .. } void foo( int i ) { library.foo( i ); } -------------------- file2.d ------------------- //don't import the library, import your overloads import file1; foo(0); //value overloaded foo(4); //default library function ------------------- Anyway, I think in most cases users aren't exactly aware of the details of library implementations, so I dunno, but I don't think that case would come up very often.Hmm yes, I guess it's a call to be made based on design philosophy, and a weighting of programmer freedom vs. potential for trouble.However, the case of debugging code and not being sure which values are overloaded and which are not is very very likely to come up, infact I think it comes up all the time.Yes, I can see your concern, but this seems like a somewhat hypothetical problem. Overloads based on type has existed for quite some time, and it really isn't hard to figure out which one is being called. For one, you simply have to step into the function instead of around it when debugging. And you can use watches to see the parameter types/values which is being passed to the function. But yes, I acknowledge that the problem will presumably be somewhat exacerbated with value-based overloading due to more widespread use and the fact that values change more easily. A keyword may ease things a bit, I give you that. But is it strictly necessary? I believe good programming practises can go a long way, while not forcing all overloads to a single physical area, it would be good manors to keep the overloads together. It would be hard to read otherwise. -Nod-
May 18 2005
Nod wrote:I know a good idea when I see one, and I believe this topic is good enough to warrant a proper discussion. I will present a short summary and a proposed integration into D. Yeah, I know it's a bit "up there"... Maybe for 3.0? :) /**************************************** * Summary: ***/ As the name suggests, value-based function overloading does function call resolution based on the *values* of the function parameters in addition to the type-based overloading already found in D. Potential benefits are: clearer, more concise code, eases refactoring, separates the special cases from main algorithm, overriding buggy libraries easy. Potential dangers are: compiler complexity, it can be misused to create unreadable code (but what can't?). For reference, these are interesting: Antti(first): http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23643 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23651 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23686 Kevin Bealer: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23745 Maybe not interesting, but explains some benefits: Me: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23732 /**************************************** * Proposed integration with D ***/ Syntax: retType funcName(paramType paramName in paramRange) { ... } Example: void foo(char ch in '\n') { ... } void foo(int i in 0..10) { ... } void foo(Bar b in null) { ... } void foo(char[] str in new Regex("/fooi/i")) { ... } Nothing complex, a new keyword, in, which tests for membership in something, and after that a list (array literal?) of values. The last one would require an operator like opIn or something overloaded. This is only a draft though. Implementation: If all data is literal, and ranges checkable at compile-time, the full overload resolution can happen at compile time. If not, the compiler needs generate the if-else[1] construct needed to select the correct function. Ambiguites are illegal. If one value range overlaps another, a compile-time error is generated. Example: void foo(int a in 0..10) { ... } void foo(int a in 5..15) { ... } // illegal, range 5..15 overlaps 0..10 Multiple parameters can resolve each other's ambiguites and as such, two overloads are only ambiguous if all parameters common between the two have ambiguities. Example: 1) void foo(int a in 0..10, char b in 'x') { ... } 2) void foo(int a in 5..15, char b in 'y') { ... } // legal 3) void foo(int a in 0..5, char b in 'y') { ... } // legal, see below 4) void foo(int a in 0..5, char b in 'x') { ... } // illegal, ambiguous to 1) Only two overloads are considered at any one time, which is why number three above is legal; while both a is ambiguous with 1), and b is ambiguous with 2), *both* are not ambiguous at the same time. If ranges are not comparable at compile time, such as when using objects with overloaded opCmp, the compiler must defer ambiguity checking until run-time. This only needs to be done in debug builds. It is not necessary for the overloaded functions to cover all possible value ranges. If a value does not fit any of the overloaded functions, either a compile-time error is emitted if data is compile-time checkable, or an OverloadExecption is thrown at run-time. Example: 1) void foo(int a in 1..4) { ... } 2) void foo(int a in 6..9) { ... } int main() { int x = 5; foo(2); // calls 1) foo(5); // complains at compile-time foo(x); // throws at run-time } /**************************************** * In closing... ***/ I think one can compare this technique with forward references. While one certainly can manage without it, heck we've done it for >10 years, it is/would be a great relief to have. So, what do you all think? -Nod- [1] For brevity, I will only mention if-else constructs, but when the value ranges allow it, a switch-case construct could be used, or some combination of the two.Hear, hear.
May 18 2005
Kyle Furlong wrote:Nod wrote:One thing that just occured to me is, cant you acheive the same goal using aserts and invariants? Isnt that what they are designed for?I know a good idea when I see one, and I believe this topic is good enough to warrant a proper discussion. I will present a short summary and a proposed integration into D. Yeah, I know it's a bit "up there"... Maybe for 3.0? :) /**************************************** * Summary: ***/ As the name suggests, value-based function overloading does function call resolution based on the *values* of the function parameters in addition to the type-based overloading already found in D. Potential benefits are: clearer, more concise code, eases refactoring, separates the special cases from main algorithm, overriding buggy libraries easy. Potential dangers are: compiler complexity, it can be misused to create unreadable code (but what can't?). For reference, these are interesting: Antti(first): http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23643 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23651 David Medlock: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23686 Kevin Bealer: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23745 Maybe not interesting, but explains some benefits: Me: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23732 /**************************************** * Proposed integration with D ***/ Syntax: retType funcName(paramType paramName in paramRange) { ... } Example: void foo(char ch in '\n') { ... } void foo(int i in 0..10) { ... } void foo(Bar b in null) { ... } void foo(char[] str in new Regex("/fooi/i")) { ... } Nothing complex, a new keyword, in, which tests for membership in something, and after that a list (array literal?) of values. The last one would require an operator like opIn or something overloaded. This is only a draft though. Implementation: If all data is literal, and ranges checkable at compile-time, the full overload resolution can happen at compile time. If not, the compiler needs generate the if-else[1] construct needed to select the correct function. Ambiguites are illegal. If one value range overlaps another, a compile-time error is generated. Example: void foo(int a in 0..10) { ... } void foo(int a in 5..15) { ... } // illegal, range 5..15 overlaps 0..10 Multiple parameters can resolve each other's ambiguites and as such, two overloads are only ambiguous if all parameters common between the two have ambiguities. Example: 1) void foo(int a in 0..10, char b in 'x') { ... } 2) void foo(int a in 5..15, char b in 'y') { ... } // legal 3) void foo(int a in 0..5, char b in 'y') { ... } // legal, see below 4) void foo(int a in 0..5, char b in 'x') { ... } // illegal, ambiguous to 1) Only two overloads are considered at any one time, which is why number three above is legal; while both a is ambiguous with 1), and b is ambiguous with 2), *both* are not ambiguous at the same time. If ranges are not comparable at compile time, such as when using objects with overloaded opCmp, the compiler must defer ambiguity checking until run-time. This only needs to be done in debug builds. It is not necessary for the overloaded functions to cover all possible value ranges. If a value does not fit any of the overloaded functions, either a compile-time error is emitted if data is compile-time checkable, or an OverloadExecption is thrown at run-time. Example: 1) void foo(int a in 1..4) { ... } 2) void foo(int a in 6..9) { ... } int main() { int x = 5; foo(2); // calls 1) foo(5); // complains at compile-time foo(x); // throws at run-time } /**************************************** * In closing... ***/ I think one can compare this technique with forward references. While one certainly can manage without it, heck we've done it for >10 years, it is/would be a great relief to have. So, what do you all think? -Nod- [1] For brevity, I will only mention if-else constructs, but when the value ranges allow it, a switch-case construct could be used, or some combination of the two.Hear, hear.
May 18 2005
In article <d6fvvg$2vq1$2 digitaldaemon.com>, Kyle Furlong says...Kyle Furlong wrote:No, asserts and invariants are only for verifying that the function/program is operating with sane values. Value-based function overloading on the other hand is a form of implicit flow control. With asserts and invariants, the compiler inserts *checks* on function invocation, which throws exceptions if the values are out-of-range. With value-based overloading *flow instructions* are inserted, so that if the values are out-of-range (so to speak) another overload is called. The two does not exclude one another. Neither do any of them supersede the other. I rather believe that they complement eachother. -Nod-Nod wrote:One thing that just occured to me is, cant you acheive the same goal using aserts and invariants? Isnt that what they are designed for?<snip>Hear, hear.
May 18 2005