Thread overview
Value-based function overloading - Take Two
May 19, 2005
Nod
May 19, 2005
Andrew Fedoniouk
May 19, 2005
Craig Black
May 19, 2005
Andrew Fedoniouk
May 19, 2005
Craig Black
May 19, 2005
Andrew Fedoniouk
May 19, 2005
Benji Smith
May 19, 2005
Nod
May 19, 2005
Nod
May 19, 2005
Hey, this went well! There has been lots of feedback, and I love feedback :) Ranges for switch statements[1] have been debated, and concerns regarding code readability and debuggability[2] has been aired. Mostly, people are positive about the idea.

Spurred by the generally positive attitude towards this idea, I will here present an addendum to the original draft, further specifying and explaining some of the dark areas, and integrating some of the feedback.

For reference, the original draft is here: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23796

/****************************************
* Defaults
***/
I didn't touch on it in the previous draft, and perhaps it is obvious, but just
for the record: When there is no range specified for a function overload, that
overload is used as a default, and is not ambiguous with any overload which has
a range specified.

Example:
void foo(int a in 0..5) { ... }
void foo(int a) { ... }  // legal, default

There can only be one default per parameter, more than one is ambiguous and an error.

Example:
void foo(int a) { ... }
void foo(int a) { ... }  // illegal, duh

Example:
void foo(int a in 0..4, char b in 'x') { ... }
void foo(int a,         char b in 'y') { ... }  // legal
void foo(int a in 5..9, char b       ) { ... }  // legal
void foo(int a,         char b in 'z') { ... }  // illegal
void foo(int a,         char b       ) { ... }  // very illegal

/****************************************
* Shadowing
***/
Implicitly, it is not possible to override overloads. That is, one cannot
accidentally create an overload which shadows another overload. This is ensured
by not allowing range ambiguities.

Explicitly, however, this can be done. By using the 'override' attribute, the function will get a higher precedence than the original overload, thus resolving any ambiguity which may exist.

Example:
void foo(int a in 0..10) { ... }
void foo(int a in 4..5) { ... }  // illegal, ambiguous
override void foo(int a in 4..5) { ... }  // legal, has higher precedence

Example:
void foo(int a in 0..10) { ... }
override void foo(int a in 4..5) { ... }

int main()
{
foo(8);  // calls first
foo(4);  // calls second
}

If the override attribute is used, the original function must exist, e.g: override void foo(int a in 0..10) { ... }  // illegal, nothing to override

Overriding overridden overloads:
This is only possible if the already-overridden overload resides in another,
imported module.

Example:
void foo(int a in 0..10) { ... }
override void foo(int a in 0..5) { ... }  // legal
override void foo(int a in 0..3) { ... }  // illegal, same module

Example:

mod.d
void foo(int a in 0..10) { ... }
override void foo(int a in 0..5) { ... }  // legal

main.d
import mod;
override void foo(int a in 0..3) { ... }  // legal

Overriding overloads is discouraged, and should be done only when absolutely necessary.

/****************************************
* Scoping
***/
Without the override keyword, regular module symbol ambiguity resolution apply.
That is, symbol ambiguities are illegal, and one must use the full module name
in the case of a symbol clash.

Example:

mod.d
void foo(int a in 5..20) { ... }

main.d
import mod;
void foo(int a in 0..15) { ... }

int main()
{
foo(10);  // calls foo in main.d
foo(20);  // illegal, foo in main.d cannot handle that value
mod.foo(10);  // calls foo in mod.d
mod.foo(0);  // illegal, foo in mod.d cannot handle that value
}

When the override keyword is used however, and no original function exists in the current module, the overload in the imported module gets overridden, and no symbol clash occurs. The overload gets "merged" with the imported module.

Example:

mod.d
void foo(int a in 5..20) { ... }

main.d
import mod;
override void foo(int a in 0..15) { ... }

int main()
{
foo(10);  // calls foo in main.d
foo(20);  // calls foo in mod.d
foo(0);  // calls foo in main.d
}

If there both exists an original function of the same name in the current module, as well as one in an imported module, then the one in the current module gets overridden. The function in the module then cannot be overridden. This situation should be rare enough to not require an easy solution.

/****************************************
* In closing...
***/
I have tried to keep the technique as generally useful as possible, and since I
value programmer freedom, I have done little to bar potential misuse[3]. This
may not be liked by all, and it may not be the right way. Do tell me what you
think.

I really need to get some sleep now.
-Nod-


[1] http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23856 [2] http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23815 [3] The following exchange with Hasan Aljudy expands on that topic. H.A: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23815 Me: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23826 H.A: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23841 Me: http://www.digitalmars.com/drn-bin/wwwnews?digitalmars.D/23853


May 19, 2005
> Example:
> void foo(int a in 0..5) { ... }
> void foo(int a) { ... }  // legal, default

Probably I don't understand hidden treasures of the approach...

If I need this I would do:

alias void function(int) foo;

static int    selector[] = [int.min, 0, 5, int.max];
static foo   foos[] =
[
    function void(int a)  { .....},
    function void(int a)  { .....},
    function void(int a)  { .....}
];

select!(selector,foos);

And I can manage by myself what is better to use: simple if-else-if, bsearch or just table in select().

Andrew.






May 19, 2005
It's just like any other high-level feature.  It can always be implemented using low level code, but it's not as clean and convenient.  Less code == greater maintainability

-Craig

"Andrew Fedoniouk" <news@terrainformatica.com> wrote in message news:d6hcgg$15ah$1@digitaldaemon.com...
>> Example:
>> void foo(int a in 0..5) { ... }
>> void foo(int a) { ... }  // legal, default
>
> Probably I don't understand hidden treasures of the approach...
>
> If I need this I would do:
>
> alias void function(int) foo;
>
> static int    selector[] = [int.min, 0, 5, int.max];
> static foo   foos[] =
> [
>    function void(int a)  { .....},
>    function void(int a)  { .....},
>    function void(int a)  { .....}
> ];
>
> select!(selector,foos);
>
> And I can manage by myself what is better to use: simple if-else-if, bsearch or just table in select().
>
> Andrew.
>
>
>
>
>
> 


May 19, 2005
"Craig Black" <cblack@ara.com> wrote in message news:d6idif$23h7$1@digitaldaemon.com...
> It's just like any other high-level feature.  It can always be implemented using low level code, but it's not as clean and convenient.  Less code == greater maintainability
>
> -Craig

" Less code == greater maintainability"
Well, it depends....

In this particular case, I wouldn't even try to maintain code having something like this:

module A;
void foo(int a in 0..5) { ... }

module B;
void foo(int a in 5..100) { ... }

Andrew.


>
> "Andrew Fedoniouk" <news@terrainformatica.com> wrote in message news:d6hcgg$15ah$1@digitaldaemon.com...
>>> Example:
>>> void foo(int a in 0..5) { ... }
>>> void foo(int a) { ... }  // legal, default
>>
>> Probably I don't understand hidden treasures of the approach...
>>
>> If I need this I would do:
>>
>> alias void function(int) foo;
>>
>> static int    selector[] = [int.min, 0, 5, int.max];
>> static foo   foos[] =
>> [
>>    function void(int a)  { .....},
>>    function void(int a)  { .....},
>>    function void(int a)  { .....}
>> ];
>>
>> select!(selector,foos);
>>
>> And I can manage by myself what is better to use: simple if-else-if, bsearch or just table in select().
>>
>> Andrew.
>>
>>
>>
>>
>>
>>
>
> 


May 19, 2005
Let me try to follow your logic.  "If a language feature allows for new ways write bad code, then this language feature should not be considered."  Is this what you are thinking here?  There are always ways to write bad code and misuse language features.  That doesn't mean that these language features are bad.

-Craig

"Andrew Fedoniouk" <news@terrainformatica.com> wrote in message news:d6ie7d$2473$1@digitaldaemon.com...
>
> "Craig Black" <cblack@ara.com> wrote in message news:d6idif$23h7$1@digitaldaemon.com...
>> It's just like any other high-level feature.  It can always be implemented using low level code, but it's not as clean and convenient. Less code == greater maintainability
>>
>> -Craig
>
> " Less code == greater maintainability"
> Well, it depends....
>
> In this particular case, I wouldn't even try to maintain code having something like this:
>
> module A;
> void foo(int a in 0..5) { ... }
>
> module B;
> void foo(int a in 5..100) { ... }
>
> Andrew.
>
>
>>
>> "Andrew Fedoniouk" <news@terrainformatica.com> wrote in message news:d6hcgg$15ah$1@digitaldaemon.com...
>>>> Example:
>>>> void foo(int a in 0..5) { ... }
>>>> void foo(int a) { ... }  // legal, default
>>>
>>> Probably I don't understand hidden treasures of the approach...
>>>
>>> If I need this I would do:
>>>
>>> alias void function(int) foo;
>>>
>>> static int    selector[] = [int.min, 0, 5, int.max];
>>> static foo   foos[] =
>>> [
>>>    function void(int a)  { .....},
>>>    function void(int a)  { .....},
>>>    function void(int a)  { .....}
>>> ];
>>>
>>> select!(selector,foos);
>>>
>>> And I can manage by myself what is better to use: simple if-else-if, bsearch or just table in select().
>>>
>>> Andrew.
>>>
>>>
>>>
>>>
>>>
>>>
>>
>>
>
> 


May 19, 2005
"Craig Black" <cblack@ara.com> wrote in message news:d6ien1$24kv$1@digitaldaemon.com...
> Let me try to follow your logic.  "If a language feature allows for new ways write bad code, then this language feature should not be considered." Is this what you are thinking here?  There are always ways to write bad code and misuse language features.  That doesn't mean that these language features are bad.
>
> -Craig

:) My statement was not so generalizing as yours...

I'll try to explain differently what I mean then:

See:
if you have switch(...) { case....} or the like you can be 100% sure
that all options are there and nothing missed.

In case of set of
void foo(int a in 5..100) { ... }
you cannot be sure. E.g. somebody forgot to include module B in
the makefile. Even in one file it will be difficult (well, not so obvious)
to
find all cases. And these are exactly "maintainance problems".

Andrew.




>
> "Andrew Fedoniouk" <news@terrainformatica.com> wrote in message news:d6ie7d$2473$1@digitaldaemon.com...
>>
>> "Craig Black" <cblack@ara.com> wrote in message news:d6idif$23h7$1@digitaldaemon.com...
>>> It's just like any other high-level feature.  It can always be implemented using low level code, but it's not as clean and convenient. Less code == greater maintainability
>>>
>>> -Craig
>>
>> " Less code == greater maintainability"
>> Well, it depends....
>>
>> In this particular case, I wouldn't even try to maintain code having something like this:
>>
>> module A;
>> void foo(int a in 0..5) { ... }
>>
>> module B;
>> void foo(int a in 5..100) { ... }
>>
>> Andrew.
>>
>>
>>>
>>> "Andrew Fedoniouk" <news@terrainformatica.com> wrote in message news:d6hcgg$15ah$1@digitaldaemon.com...
>>>>> Example:
>>>>> void foo(int a in 0..5) { ... }
>>>>> void foo(int a) { ... }  // legal, default
>>>>
>>>> Probably I don't understand hidden treasures of the approach...
>>>>
>>>> If I need this I would do:
>>>>
>>>> alias void function(int) foo;
>>>>
>>>> static int    selector[] = [int.min, 0, 5, int.max];
>>>> static foo   foos[] =
>>>> [
>>>>    function void(int a)  { .....},
>>>>    function void(int a)  { .....},
>>>>    function void(int a)  { .....}
>>>> ];
>>>>
>>>> select!(selector,foos);
>>>>
>>>> And I can manage by myself what is better to use: simple if-else-if, bsearch or just table in select().
>>>>
>>>> Andrew.
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>
>>>
>>
>>
>
> 


May 19, 2005
In article <d6hcgg$15ah$1@digitaldaemon.com>, Andrew Fedoniouk says...
>
>> Example:
>> void foo(int a in 0..5) { ... }
>> void foo(int a) { ... }  // legal, default
>
>Probably I don't understand hidden treasures of the approach...
>
>If I need this I would do:
>
>alias void function(int) foo;
>
>static int    selector[] = [int.min, 0, 5, int.max];
>static foo   foos[] =
>[
>    function void(int a)  { .....},
>    function void(int a)  { .....},
>    function void(int a)  { .....}
>];
>
>select!(selector,foos);
>
>And I can manage by myself what is better to use: simple if-else-if, bsearch or just table in select().
>
>Andrew.
>
>

The hidden treasures lie in easing maintenance and refactoring. Consider for example splitting a function - turning it into two, because the first one has gotten too complex.

In the normal case, one would have to find all uses of the function, and exchange it for an if/else or switch/case, or indeed - your little snippet. This can be nontrivial for a large project, and if you're not familiar with the codebase, even possibly hazardous.

In the case of value-based overloading, you simply split the function right there and then, and no other code needs to be touched. The overload redirection is inserted by the compiler, and all other code is oblivious to the change.

More generally speaking, by transferring the decision of which values a function can handle out of the code and into the function, the code becomes more modular.


It is true one could simply use the following construct to successfully emulate the behaviour:

void foo(int a)
{
if (a < 0)
{ ... }
else if (a >= 0)
{ ... }
}

But then the code is still *fixed in its framework* so to speak; you lose forward referencing, and the ability to easily move code about. And it isn't pretty either :)

-Nod-


May 19, 2005
Craig Black wrote:
> Let me try to follow your logic.  "If a language feature allows for new ways write bad code, then this language feature should not be considered."  Is this what you are thinking here?  There are always ways to write bad code and misuse language features.  That doesn't mean that these language features are bad.
> 
> -Craig

If a new language feature...

1) Doesn't really solve any existing problem with the language, but rather just provides new syntax for something that's already possible.

AND

2) Creates the opportunity to write confusing code with hidden dependencies and invisible control-flow logic.

THEN

Yes, I would prefer that such a language feature should not be considered. Yeah, there are always ways to write bad code, but it seems like we should minimize those cases, except in circumstances where the new feature actually ADDS SOMETHING IMPORTANT to the language.

--BenjiSmith
May 19, 2005
In article <d6ie7d$2473$1@digitaldaemon.com>, Andrew Fedoniouk says...
>
>
>"Craig Black" <cblack@ara.com> wrote in message news:d6idif$23h7$1@digitaldaemon.com...
>> It's just like any other high-level feature.  It can always be implemented using low level code, but it's not as clean and convenient.  Less code == greater maintainability
>>
>> -Craig
>
>" Less code == greater maintainability"
>Well, it depends....
>
>In this particular case, I wouldn't even try to maintain code having something like this:
>
>module A;
>void foo(int a in 0..5) { ... }
>
>module B;
>void foo(int a in 5..100) { ... }
>
>Andrew.
>
>> <snip>
>

I agree that that *would* be a maintenance problem. However, any feature can be misused, and doing what you suggest should be highly discouraged.

And that should read:

module A;
void foo(int a in 0..5) { ... }

module B;
override void foo(int a in 5..100) { ... }

Read the spec ye lazy ass flame-fountains! :)