Thread overview
Re: discrimination of constructors with same number of parameters
Dec 30, 2010
Jonathan M Davis
Dec 30, 2010
bearophile
Dec 30, 2010
Guilherme Vieira
Dec 30, 2010
Lutger Blijdestijn
Dec 30, 2010
spir
Dec 30, 2010
Lutger Blijdestijn
Dec 30, 2010
spir
Dec 30, 2010
spir
December 30, 2010
On Thursday 30 December 2010 02:50:55 spir wrote:
> Hello,
> 
> 
> When 2 constructors (*) accept the same number of parameters, the only
> remaining discrimination is type. Right? But some language types (or
> machine types) can have very diverse _human_ semantics, and thus be used
> for various purposes which should, but cannot, be considered different:
> this (int[] data, string filename) {...}
> 	this (int[] data, string message) {...}
> Aliasing like in
> 	alias string Name;
> does not help since for D Name is still string.
> 
> I know about typedef, but it is not even mentionned in TDPL, so I guess it
> is on the deprecation path. (Am I right?) So, what is the solution for
> this? (I added a 3rd fake bool parameter in one case)
> 
> Things get more complicated with unsigned integers: they can be used as
> ordinals (index, which one), as cardinals (count, how many), as any of the
> char types. These are completely different semantics for the "modeller"
> (the programmer), but for the language (thus for the machine) they are the
> same semantics.
> 
> Things get worse with template parameterisation, a case I lately met:
>     Struct S (Element) {
> 	this (int[] data, string message) {...}
> 	this (int[] data, Element element) {...}
> What happens when Element is string? Below an example:
> 
> struct S(Typ) {
>     this(int) {writeln("int");}
>     this(Typ) {writeln("Typ");}
> }
> unittest {
>     auto s1 = S!string(1);
>     auto s1 = S!int(1);
> }
> ==>
> rdmd -w -debug -unittest -L--export-dynamic --build-only -of"__trials__"
> "__trials__.d"
> 
> __trials__.d(42): Error: constructor __trials__.S!(int).S.this called with
> argument types: ((int))
> matches both:
> 	__trials__.S!(int).S.this(int _param_0)
> and:
> 	__trials__.S!(int).S.this(int _param_0)
> 
> Compilation failed.
> 
> How do you cope with such cases?
> 
> Denis
> 
> (*) or any other func, in fact, but the issue shows up more frequently on constructors, because they have good reasons to accept various parameter sets.

This is a common issue in programming languages which allow for function overloading. Type is what's used to determine which overload to use. If you want to have two overloads that use the same types, then you're out of luck. That generally means either creating another function or creating a new type (and creating a new type is generally overkill). typedef is definitely on the way out, so that's not a solution, and it would be a pretty fragile one IMHO anyway. So, what you would do normally is create another function with another name.

In the case of constructors, you can't do that. So, if you really need it, you create static factory methods which return a new value of that type. The factory methods can have different names.

But function overloading works on type. So, if two overloads would crash, you either have to create a new function with a new name, or you have to create a new type. It may be annoying sometimes, but it's still a whale of a lot better than not having function overloading at all - as is the case with languages such as C and Go.

- Jonathan M Davis
December 30, 2010
Jonathan M Davis:

> typedef is definitely on the way out, so that's not a solution,

typedef is deprecated (because its semantics is not flexible enough and because it doesn't play well with object oriented language features), but I have a real need for something like it. Andrei has discussed about a Phobos-based typedef replacement (based on structs + alias this), but nothing concrete has come out yet. I hope to see something to solve problems like spir ones.


> and it would be a pretty fragile one IMHO anyway.

Please, explain better.

Bye,
bearophile
December 30, 2010
On Thu, Dec 30, 2010 at 9:24 AM, bearophile <bearophileHUGS@lycos.com>wrote:

> Jonathan M Davis:
>
> > typedef is definitely on the way out, so that's not a solution,
>
> typedef is deprecated (because its semantics is not flexible enough and because it doesn't play well with object oriented language features), but I have a real need for something like it. Andrei has discussed about a Phobos-based typedef replacement (based on structs + alias this), but nothing concrete has come out yet. I hope to see something to solve problems like spir ones.
>
>
> > and it would be a pretty fragile one IMHO anyway.
>
> Please, explain better.
>
> Bye,
> bearophile
>

As far as I know, typedef was a form of "discriminated alias". I don't know the reasons for its deprecation. It just occurred to me that D's typedefs + templates could be quite handy in this case.

Consider:

struct semantic_wrapper(T)
{
    this(T value) { this.value = value; }

    T value;
}

typedef semantic_wrapper!(int) Position;
typedef semantic_wrapper!(size_t) Count;
typedef semantic_wrapper!(string) Filename;
typedef semantic_wrapper!(string) DirPath;

void func(Position pos) { ... }
void func(Count c) { ... }
void func(Filename fname) { ... }
void func(DirPath dir) { ... }

void main()
{
    func(Position(1)); // calls first overload
    func(Count(5)); // calls second
    func(Filename("file.txt")); // third
    func(DirPath("/dev/null")); // fourth

    func(1); // fails
    func("blah"); // fails
}


Requires a little more typing, but sometimes it can be better than creating a new function name (which can get extra-big, non-telling or both) or than creating factory methods (which I personally dislike, although it's just a matter of taste most of the time; sometimes you may want to instantiate from inside a template and classes needing factories would not work, for example, but one could argue on the validity of this anytime).

Just giving my 2 cents. Dunno if I missed some detail.

-- 
Atenciosamente / Sincerely,
Guilherme ("n2liquid") Vieira


December 30, 2010
On Thu, 30 Dec 2010 03:01:52 -0800
Jonathan M Davis <jmdavisProg@gmx.com> wrote:

> On Thursday 30 December 2010 02:50:55 spir wrote:
> > Hello,
> > 
> > 
> > When 2 constructors (*) accept the same number of parameters, the only
> > remaining discrimination is type. Right? But some language types (or
> > machine types) can have very diverse _human_ semantics, and thus be used
> > for various purposes which should, but cannot, be considered different:
> > this (int[] data, string filename) {...}
> > 	this (int[] data, string message) {...}
> > Aliasing like in
> > 	alias string Name;
> > does not help since for D Name is still string.
> > 
> > I know about typedef, but it is not even mentionned in TDPL, so I guess it
> > is on the deprecation path. (Am I right?) So, what is the solution for
> > this? (I added a 3rd fake bool parameter in one case)
> > 
> > Things get more complicated with unsigned integers: they can be used as
> > ordinals (index, which one), as cardinals (count, how many), as any of the
> > char types. These are completely different semantics for the "modeller"
> > (the programmer), but for the language (thus for the machine) they are the
> > same semantics.
> > 
> > Things get worse with template parameterisation, a case I lately met:
> >     Struct S (Element) {
> > 	this (int[] data, string message) {...}
> > 	this (int[] data, Element element) {...}
> > What happens when Element is string? Below an example:
> > 
> > struct S(Typ) {
> >     this(int) {writeln("int");}
> >     this(Typ) {writeln("Typ");}
> > }
> > unittest {
> >     auto s1 = S!string(1);
> >     auto s1 = S!int(1);
> > }
> > ==>
> > rdmd -w -debug -unittest -L--export-dynamic --build-only -of"__trials__"
> > "__trials__.d"
> > 
> > __trials__.d(42): Error: constructor __trials__.S!(int).S.this called with
> > argument types: ((int))
> > matches both:
> > 	__trials__.S!(int).S.this(int _param_0)
> > and:
> > 	__trials__.S!(int).S.this(int _param_0)
> > 
> > Compilation failed.
> > 
> > How do you cope with such cases?
> > 
> > Denis
> > 
> > (*) or any other func, in fact, but the issue shows up more frequently on constructors, because they have good reasons to accept various parameter sets.
> 
> This is a common issue in programming languages which allow for function overloading. Type is what's used to determine which overload to use. If you want to have two overloads that use the same types, then you're out of luck. That generally means either creating another function or creating a new type (and creating a new type is generally overkill). typedef is definitely on the way out, so that's not a solution, and it would be a pretty fragile one IMHO anyway. So, what you would do normally is create another function with another name.
> 
> In the case of constructors, you can't do that. So, if you really need it, you create static factory methods which return a new value of that type. The factory methods can have different names.
> 
> But function overloading works on type. So, if two overloads would crash, you either have to create a new function with a new name, or you have to create a new type. It may be annoying sometimes, but it's still a whale of a lot better than not having function overloading at all - as is the case with languages such as C and Go.

Thank you very much for this clear explanation, jonathan.

Could someone expand on the reason(s) why "typedef is definitely on the way out"? I can see pratical and conceptual advantages of having "exclusive" type defs; I mean a variant of alias for which the aliased type cannot be used where the newly type is expected.
1. Practically, this solves the above issue. For instance "typedef string Name" would discriminate two constructors.
2. Conceptually, a Name for instance is nothing like a string in general (and a message in particular). Letting the programmer use the proper is a great gain in code clarity. (See Pascal like languages for this practice.)
Additionally, this would avoid bugs where an element of a given conceptual type is used in place of another (and both happen to be the same machine type, or be compatible via casting). I have no idea how common such bugs are; but the situation is similar to frequent bugs in dynamic languages: you can pass anything to a func, thus if the operations performed there happen to accept what is passed, the bug is silently ignored.

Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

December 30, 2010
On Thu, 30 Dec 2010 10:07:58 -0200
Guilherme Vieira <n2.nitrogen@gmail.com> wrote:

> On Thu, Dec 30, 2010 at 9:24 AM, bearophile <bearophileHUGS@lycos.com>wrote:
> 
> > Jonathan M Davis:
> >
> > > typedef is definitely on the way out, so that's not a solution,
> >
> > typedef is deprecated (because its semantics is not flexible enough and because it doesn't play well with object oriented language features), but I have a real need for something like it. Andrei has discussed about a Phobos-based typedef replacement (based on structs + alias this), but nothing concrete has come out yet. I hope to see something to solve problems like spir ones.
> >
> >
> > > and it would be a pretty fragile one IMHO anyway.
> >
> > Please, explain better.
> >
> > Bye,
> > bearophile
> >
> 
> As far as I know, typedef was a form of "discriminated alias". I don't know the reasons for its deprecation. It just occurred to me that D's typedefs + templates could be quite handy in this case.
> 
> Consider:
> 
> struct semantic_wrapper(T)
> {
>     this(T value) { this.value = value; }
> 
>     T value;
> }
> 
> typedef semantic_wrapper!(int) Position;
> typedef semantic_wrapper!(size_t) Count;
> typedef semantic_wrapper!(string) Filename;
> typedef semantic_wrapper!(string) DirPath;
> 
> void func(Position pos) { ... }
> void func(Count c) { ... }
> void func(Filename fname) { ... }
> void func(DirPath dir) { ... }
> 
> void main()
> {
>     func(Position(1)); // calls first overload
>     func(Count(5)); // calls second
>     func(Filename("file.txt")); // third
>     func(DirPath("/dev/null")); // fourth
> 
>     func(1); // fails
>     func("blah"); // fails
> }
> 
> 
> Requires a little more typing, but sometimes it can be better than creating a new function name (which can get extra-big, non-telling or both) or than creating factory methods (which I personally dislike, although it's just a matter of taste most of the time; sometimes you may want to instantiate from inside a template and classes needing factories would not work, for example, but one could argue on the validity of this anytime).
> 
> Just giving my 2 cents. Dunno if I missed some detail.

I would have several needs for wrapper structs or classes like yours.

I like very much your proper use of proper terms (here type names): I myself make a difference between machine types and conceptual types. For instance, I always define:
    // standard type aliases
    alias sizediff_t Ordinal;   // index, which one
    alias size_t Cardinal;      // count, how many
and use exclusively Ordinal and Cardinal in code. But this would be better with a discriminating instruction. the issue, then, is with literals:
* Either such types must be defined by the language, and literals are properly cast (like in the case of int vs uint vs dchar for instance).
* Or literal casting works even with discriminating/exclusive types (which don't mutually cast); meaning eg a string literal would be accepted where a Name is expected even if string's do not cast to Name's.

What do you think of the points I mention in favor of a "discriminating alias" in a // post?

Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

December 30, 2010
Guilherme Vieira wrote:

> On Thu, Dec 30, 2010 at 9:24 AM, bearophile <bearophileHUGS@lycos.com>wrote:
> 
>> Jonathan M Davis:
>>
>> > typedef is definitely on the way out, so that's not a solution,
>>
>> typedef is deprecated (because its semantics is not flexible enough and because it doesn't play well with object oriented language features), but I have a real need for something like it. Andrei has discussed about a Phobos-based typedef replacement (based on structs + alias this), but nothing concrete has come out yet. I hope to see something to solve problems like spir ones.
>>
>>
>> > and it would be a pretty fragile one IMHO anyway.
>>
>> Please, explain better.
>>
>> Bye,
>> bearophile
>>
> 
> As far as I know, typedef was a form of "discriminated alias". I don't know the reasons for its deprecation. It just occurred to me that D's typedefs + templates could be quite handy in this case.
> 
> Consider:
> 
> struct semantic_wrapper(T)
> {
>     this(T value) { this.value = value; }
> 
>     T value;
> }
> 
> typedef semantic_wrapper!(int) Position;
> typedef semantic_wrapper!(size_t) Count;
> typedef semantic_wrapper!(string) Filename;
> typedef semantic_wrapper!(string) DirPath;
> 
> void func(Position pos) { ... }
> void func(Count c) { ... }
> void func(Filename fname) { ... }
> void func(DirPath dir) { ... }
> 
> void main()
> {
>     func(Position(1)); // calls first overload
>     func(Count(5)); // calls second
>     func(Filename("file.txt")); // third
>     func(DirPath("/dev/null")); // fourth
> 
>     func(1); // fails
>     func("blah"); // fails
> }
> 
> 
> Requires a little more typing, but sometimes it can be better than creating a new function name (which can get extra-big, non-telling or both) or than creating factory methods (which I personally dislike, although it's just a matter of taste most of the time; sometimes you may want to instantiate from inside a template and classes needing factories would not work, for example, but one could argue on the validity of this anytime).
> 
> Just giving my 2 cents. Dunno if I missed some detail.
> 

Here is an attempt to implement it, still needs support for writeln and lacks some details:

import std.stdio;

mixin template Newtype(T, string typename)
{
    mixin("struct " ~ typename ~ " { alias base this; " ~ T.stringof ~
          " base;  @disable void opAssign(" ~ T.stringof ~ ") {} }");
}

mixin Newtype!(int, "Position");
mixin Newtype!(size_t, "Count");
mixin Newtype!(string, "FileName");
mixin Newtype!(string, "DirPath");

void func(Position pos) { writeln("position: ", pos.base ); }
void func(Count c) { writeln("count:", c.base); }
void func(FileName fname) { writeln("filename:", fname.base); }
void func(DirPath dir) { writeln("dirpath:", dir.base); }

void func2(int pos) { writeln("position: ", pos); }

void main()
{

    func(Position(1)); // calls first overload
    func2(Position(1)); // implicit conversion to int with alias this
    func(Count(5)); // calls second
    func(FileName("file.txt")); // third
    func(DirPath("/dev/null")); // fourth
    func(1); // fails
    func("blah"); // fails

    auto p = Position(1);
    p = 2; // fails
    p.base = 4; // ok, explicit
}
December 30, 2010
On Thu, 30 Dec 2010 14:39:02 +0100
Lutger Blijdestijn <lutger.blijdestijn@gmail.com> wrote:

> Guilherme Vieira wrote:
> 
> > On Thu, Dec 30, 2010 at 9:24 AM, bearophile <bearophileHUGS@lycos.com>wrote:
> > 
> >> Jonathan M Davis:
> >>
> >> > typedef is definitely on the way out, so that's not a solution,
> >>
> >> typedef is deprecated (because its semantics is not flexible enough and because it doesn't play well with object oriented language features), but I have a real need for something like it. Andrei has discussed about a Phobos-based typedef replacement (based on structs + alias this), but nothing concrete has come out yet. I hope to see something to solve problems like spir ones.
> >>
> >>
> >> > and it would be a pretty fragile one IMHO anyway.
> >>
> >> Please, explain better.
> >>
> >> Bye,
> >> bearophile
> >>
> > 
> > As far as I know, typedef was a form of "discriminated alias". I don't know the reasons for its deprecation. It just occurred to me that D's typedefs + templates could be quite handy in this case.
> > 
> > Consider:
> > 
> > struct semantic_wrapper(T)
> > {
> >     this(T value) { this.value = value; }
> > 
> >     T value;
> > }
> > 
> > typedef semantic_wrapper!(int) Position;
> > typedef semantic_wrapper!(size_t) Count;
> > typedef semantic_wrapper!(string) Filename;
> > typedef semantic_wrapper!(string) DirPath;
> > 
> > void func(Position pos) { ... }
> > void func(Count c) { ... }
> > void func(Filename fname) { ... }
> > void func(DirPath dir) { ... }
> > 
> > void main()
> > {
> >     func(Position(1)); // calls first overload
> >     func(Count(5)); // calls second
> >     func(Filename("file.txt")); // third
> >     func(DirPath("/dev/null")); // fourth
> > 
> >     func(1); // fails
> >     func("blah"); // fails
> > }
> > 
> > 
> > Requires a little more typing, but sometimes it can be better than creating a new function name (which can get extra-big, non-telling or both) or than creating factory methods (which I personally dislike, although it's just a matter of taste most of the time; sometimes you may want to instantiate from inside a template and classes needing factories would not work, for example, but one could argue on the validity of this anytime).
> > 
> > Just giving my 2 cents. Dunno if I missed some detail.
> > 
> 
> Here is an attempt to implement it, still needs support for writeln and lacks some details:
> 
> import std.stdio;
> 
> mixin template Newtype(T, string typename)
> {
>     mixin("struct " ~ typename ~ " { alias base this; " ~ T.stringof ~
>           " base;  @disable void opAssign(" ~ T.stringof ~ ") {} }");
> }
> 
> mixin Newtype!(int, "Position");
> mixin Newtype!(size_t, "Count");
> mixin Newtype!(string, "FileName");
> mixin Newtype!(string, "DirPath");
> 
> void func(Position pos) { writeln("position: ", pos.base ); }
> void func(Count c) { writeln("count:", c.base); }
> void func(FileName fname) { writeln("filename:", fname.base); }
> void func(DirPath dir) { writeln("dirpath:", dir.base); }
> 
> void func2(int pos) { writeln("position: ", pos); }
> 
> void main()
> {
> 
>     func(Position(1)); // calls first overload
>     func2(Position(1)); // implicit conversion to int with alias this
>     func(Count(5)); // calls second
>     func(FileName("file.txt")); // third
>     func(DirPath("/dev/null")); // fourth
>     func(1); // fails
>     func("blah"); // fails
> 
>     auto p = Position(1);
>     p = 2; // fails
>     p.base = 4; // ok, explicit
> }

I like very much the template mixin solution. Would there be any difference in inheriting an interface (or even a plain type)?
Also, can one presently rewrite this in D without _string_ mixin inside the template? (Else this solution is simply not acceptable for me: I'm allergic to code in strings; but don't ask me why ;-)

Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

December 30, 2010
spir wrote:

(...)
> 
> I like very much the template mixin solution. Would there be any difference in inheriting an interface (or even a plain type)? Also, can one presently rewrite this in D without _string_ mixin inside the template? (Else this solution is simply not acceptable for me: I'm allergic to code in strings; but don't ask me why ;-)
> 
> Denis
> -- -- -- -- -- -- --
> vit esse estrany ☣
> 
> spir.wikidot.com

Compared to inheriting interfaces:
- works with primitive and struct types
- no dynamic polymorphism
- no performance penalty, likely less code size

You can do it without string mixins by explicitly writing the code the mixin automates. This should also work more or less, but I find it less clear:

struct NewType(T, string typename)
{
    alias T this;
    T base;
    @disable void opAssign(T) {}
}

The typename parameter is arbitrary here, just to create a seperate type for each possible value. It might as well be an integer.

alias NewType!(int, "Position") Position;
void func(Position position) { ... }

or used directly:

void func(Newtype!(int, "Position") position) { ... }