www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - MIID Mixins auto aliasing

reply "Ilya Zaitseff" <sark7 mail333.com> writes:
I really love mixins, but there are one annoying thing in D - dirty work  
for aliasing (for overload)
_each_ function mixed in.
For example, in my vector library, I have one template consists of many  
functions. That template mixed in
module scope for each vector type. At now I must write alias for each  
functions, and when count of these
functions grow, it becomes a pain.

And what if I have 100 functions? I do not want to type each function :(.

template TMixin(T)
{
   void foo1() {}
   void foo2() {}
   ...
   void foo100() {}
}

mixin TMixin(T1) Mixin1;
alias Mixin1.foo1 foo1;
alias Mixin1.foo1 foo2;
...
alias Mixin1.foo100 foo100;

mixin TMixin(T2) Mixin2;
alias Mixin2.foo1 foo1;
alias Mixin2.foo1 foo2;
...
alias Mixin2.foo100 foo100;

I need for:

mixin TMixin(T1) alias;

or

mixin TMixin(T1) Mixin1;
alias Mixin1.* *;

or something simular by functionality.
Sep 21 2004
parent reply Deja Augustine <deja scratch-ware.net> writes:
um... perhaps I misunderstand how mixins are used, but you're using them 
like template instances which is not even valid.

As I understood it (and thus implemented it in D.NET) mixins ARE the 
auto aliasing for template instances.  To use your example:

 template TMixin(T)
 {
   void foo1() {}
   void foo2() {}
   ...
   void foo100() {}
 }

 mixin TMixin(T1);

actually generates:
 void foo1() {}
 void foo2() {}
 ...
 void foo100() {}

in the scope in which it is declared. Of course, rereading over your post I'm not sure what the impact is on function overloads, so I may be addressing the wrong issue. -Deja Ilya Zaitseff wrote:
 I really love mixins, but there are one annoying thing in D - dirty 
 work  for aliasing (for overload)
 _each_ function mixed in.
 For example, in my vector library, I have one template consists of many  
 functions. That template mixed in
 module scope for each vector type. At now I must write alias for each  
 functions, and when count of these
 functions grow, it becomes a pain.
 
 And what if I have 100 functions? I do not want to type each function :(.
 
 template TMixin(T)
 {
   void foo1() {}
   void foo2() {}
   ...
   void foo100() {}
 }
 
 mixin TMixin(T1) Mixin1;
 alias Mixin1.foo1 foo1;
 alias Mixin1.foo1 foo2;
 ...
 alias Mixin1.foo100 foo100;
 
 mixin TMixin(T2) Mixin2;
 alias Mixin2.foo1 foo1;
 alias Mixin2.foo1 foo2;
 ...
 alias Mixin2.foo100 foo100;
 
 I need for:
 
 mixin TMixin(T1) alias;
 
 or
 
 mixin TMixin(T1) Mixin1;
 alias Mixin1.* *;
 
 or something simular by functionality.

Sep 21 2004
parent reply Sean Kelly <sean f4.ca> writes:
In article <cir7db$1i1n$1 digitaldaemon.com>, Deja Augustine says...
um... perhaps I misunderstand how mixins are used, but you're using them 
like template instances which is not even valid.

As I understood it (and thus implemented it in D.NET) mixins ARE the 
auto aliasing for template instances.  To use your example:

 template TMixin(T)
 {
   void foo1() {}
   void foo2() {}
   ...
   void foo100() {}
 }

 mixin TMixin(T1);

actually generates:
 void foo1() {}
 void foo2() {}
 ...
 void foo100() {}

in the scope in which it is declared. Of course, rereading over your post I'm not sure what the impact is on function overloads, so I may be addressing the wrong issue.

Mixins work like imports. That is, they aren't automatically considered for overload resolution. Here's an example: # template T() { # void func(char); # } # # class C { # public: # mixin T!(); # void func(int); # } # # C c = new C(); # c.func('a'); In the above, C.func(int) will be called. In order to have the mixin function available for overload resolution it has to be aliased in that scope. This is conceptually similar to subclassing: # class A { # public: # void func(char); # } # # class B : A { # public: # void func(int); # } # # B b = new B(); # b.func('a'); In the above example, func(int) will be called as well. Note that in both cases, func(char) would be visible if func(int) were not declared. Sean
Sep 22 2004
parent reply Ant <duitoolkit yahoo.ca> writes:
On Wed, 22 Sep 2004 15:06:01 +0000, Sean Kelly wrote:

Of course, rereading over your post I'm not sure what the impact is on 
function overloads, so I may be addressing the wrong issue.

This is conceptually similar to subclassing: # class A { # public: # void func(char); # } # # class B : A { # public: # void func(int); # } # # B b = new B(); # b.func('a'); In the above example, func(int) will be called as well. Note that in both cases, func(char) would be visible if func(int) were not declared.

this wrong! from the docs: " Integer Promotions The following types are implicitly converted to int: bit byte ubyte short ushort enum char wchar dchar " if we replace b.func(int) by b.func(float) it's still called! it's a bug. I'm posting it on the bugs group. (If it's a feature it should be revised.) Ant
Sep 22 2004
parent reply Sean Kelly <sean f4.ca> writes:
In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...
if we replace b.func(int) by b.func(float) it's still called!
it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++). Sean
Sep 22 2004
next sibling parent reply Sjoerd van Leent <svanleent wanadoo.nl> writes:
Sean Kelly wrote:
 In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...
 
if we replace b.func(int) by b.func(float) it's still called!
it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++). Sean

means that when overriding you should only be able to call the function b.func(float) because it is the only visible one. If you want to be able to call b.func(char) or b.func(int) as well you need to specify this explicitly with the override attribute in the subclass. Regards, Sjoerd
Sep 22 2004
next sibling parent reply Deja Augustine <deja scratch-ware.net> writes:
Sjoerd van Leent wrote:

 Sean Kelly wrote:
 
 In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...

 if we replace b.func(int) by b.func(float) it's still called!
 it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++). Sean

means that when overriding you should only be able to call the function b.func(float) because it is the only visible one. If you want to be able to call b.func(char) or b.func(int) as well you need to specify this explicitly with the override attribute in the subclass. Regards, Sjoerd

The override attribute does nothing to change the functionality (unless that's recently changed). A function marked "override" doesn't behave any differently from one without it. It's there purely as a debugging tool. To quote the documentation: "...It means that the function must override a function with the same name and parameters in a base class. The override attribute is useful for catching errors when a base class's member function gets its parameters changed, and all derived classes need to have their overriding functions updated." All it does is generate a compiler error if a subclass, of the class with the "override" method, does not implement a version of that method. It's something akin to a pure virtual function except that it can have an implementation in the base class. -Deja
Sep 22 2004
parent Sjoerd van Leent <svanleent wanadoo.nl> writes:
Deja Augustine wrote:
 Sjoerd van Leent wrote:
 
 Sean Kelly wrote:

 In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...

 if we replace b.func(int) by b.func(float) it's still called!
 it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++). Sean

means that when overriding you should only be able to call the function b.func(float) because it is the only visible one. If you want to be able to call b.func(char) or b.func(int) as well you need to specify this explicitly with the override attribute in the subclass. Regards, Sjoerd

The override attribute does nothing to change the functionality (unless that's recently changed). A function marked "override" doesn't behave any differently from one without it. It's there purely as a debugging tool. To quote the documentation: "...It means that the function must override a function with the same name and parameters in a base class. The override attribute is useful for catching errors when a base class's member function gets its parameters changed, and all derived classes need to have their overriding functions updated." All it does is generate a compiler error if a subclass, of the class with the "override" method, does not implement a version of that method. It's something akin to a pure virtual function except that it can have an implementation in the base class. -Deja

Maybe my explanation lacked a little. With override I did mean what you said: "...It means that the function must override a function with the same name and parameters in a base class..." It enforces you to use the same parameters, so you need to specify b.func(int). As I am rereading the case, it seems that a mixin would be better, because you only need one rule, not a complete reimplementation. Regards, Sjoerd
Sep 22 2004
prev sibling parent Ant <duitoolkit yahoo.ca> writes:
On Wed, 22 Sep 2004 20:59:56 +0200, Sjoerd van Leent wrote:

 Sean Kelly wrote:
 In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...
 
if we replace b.func(int) by b.func(float) it's still called!
it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++). Sean

means that when overriding you should only be able to call the function b.func(float) because it is the only visible one. If you want to be able to call b.func(char) or b.func(int) as well you need to specify this explicitly with the override attribute in the subclass. Regards, Sjoerd

Disagreed, the signature of the function should be consired first, the the automatic conversion. you are not overrinding you are overloading. it's not the only visible, they have different signatures. if you want to call an int function with a char to func(cast(int) c); Ant
Sep 22 2004
prev sibling parent reply Ant <duitoolkit yahoo.ca> writes:
On Wed, 22 Sep 2004 18:40:14 +0000, Sean Kelly wrote:

 In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...
if we replace b.func(int) by b.func(float) it's still called!
it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++). Sean

this is nonsence, I expect the right function to be called, not the one that is "closer"... let's call it nonsence and convince Walter to change it. who can write a justification and present it on a PDF? Why do we have char converted to int in the first place? what is the justification (besides historical reasons?) this is what you should expect from a strong type language: ################## public class Over { static class A { void func(char a) { System.out.println("A.func"); } } static class B extends A { void func(int i) { System.out.println("B.func"); } } public static void main(String[] args) { B b = new B(); b.func('a'); } } ################### $ javac Over.java $ java Over A.func ################### (I don't care how C++ does it (some might care)) Ant
Sep 22 2004
next sibling parent Nick <Nick_member pathlink.com> writes:
In article <pan.2004.09.22.20.05.59.245040 yahoo.ca>, Ant says...
this is nonsence, I expect the right function to be called,
not the one that is "closer"...

This discussion has been raging on this group for a while, but seems to have died down for now (and thank $deity for that :-) The issue is less clear cut and more complex than you probably realize at first glance, and both the Java way (which you are promoting) and the C++ way (the way it is now) have their pros and cons. Both can give you non-obvious and hard-to-catch bugs in certain cases. I personally prefer the way it is now, though, since it gives the option of "removing" inherrited members. If you want to the "correct" function to be called in your example, add # class B : A # { # alias A.func func; # ... # } (But you probably knew that?)
let's call it nonsence and convince Walter to change it.
who can write a justification and present it on a PDF?

Someone already tried that. It didn't work <g>. The author now calls himself antiAlias, IIRC.
Why do we have char converted to int in the first place?
what is the justification (besides historical reasons?)

Hmm, that's a good point. Some people are arguing that char should be something different from in C/C++ and Java. To make the difference more clear, maybe numeric manipulation of (w/d)chars should require an explicit cast? Nick
Sep 22 2004
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
In article <pan.2004.09.22.20.05.59.245040 yahoo.ca>, Ant says...
On Wed, 22 Sep 2004 18:40:14 +0000, Sean Kelly wrote:

 In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...
if we replace b.func(int) by b.func(float) it's still called!
it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++).

this is nonsence, I expect the right function to be called, not the one that is "closer"... let's call it nonsence and convince Walter to change it. who can write a justification and present it on a PDF?

I don't consider it nonsense. Consider this example (pulled from "The Design and Evolution of C++"): # class X { # int x; # public: # void copy( X p ) { x = p.x; } # } # # class XX : X { # int xx; # public: # void copy( XX p ) { xx = p.xx; X.copy( p ); } # } # # void f( X a, XX b ) { # a.copy( b ); // ok, copy X part of B # b.copy( a ); // error, copy(X) is hidden by copy(XX) # } "Allowing the second copy operation, as would happen if base and derived scopes were merged, would cause b's state to be partially updated. In most real cases, this would lead to very strange behavior of operations on XX objects." ie. the partial copy could result in a violation of class invariants and IMO is certainly undesired behavior. The visibility rules force the programmer to explicitly specify which functions won't do horrible things if they are made visible in the derived function's scope.
Why do we have char converted to int in the first place?
what is the justification (besides historical reasons?)

Consider this expression: short s = 1; int x = s * 5; Without implicit type promotion rules the only way this would be possible is with a cast: int x = cast(int) s * 5; I suppose it is arguable whether a D char should be treated the same as a C char with respect to promotion rules, but I don't have a major problem with it.
(I don't care how C++ does it (some might care))

Thing is, there is generally a good reason for how C++ does something. That reason may be buried in the annals of time, but that doesn't make it any less applicable today than it was 20 years ago. If you've got the will, I strongly recommend picking up "The Design and Evolution of C++" by Bjarne Stroustrup. The book covers pretty much every feature that was even considered for C++ and an explanation of why it was chosen/rejected, how the design changed over time, etc. If you want to know why something is the way it is, that's the best place I've found to look. Sean
Sep 22 2004
parent reply Ant <duitoolkit yahoo.ca> writes:
On Wed, 22 Sep 2004 21:38:02 +0000, Sean Kelly wrote:

 In article <pan.2004.09.22.20.05.59.245040 yahoo.ca>, Ant says...
On Wed, 22 Sep 2004 18:40:14 +0000, Sean Kelly wrote:

 In article <pan.2004.09.22.15.56.33.270961 yahoo.ca>, Ant says...
if we replace b.func(int) by b.func(float) it's still called!
it's a bug.

It's not a bug, it's how function visibility and overload resolution is handled in D (and in C++).

this is nonsence, I expect the right function to be called, not the one that is "closer"... let's call it nonsence and convince Walter to change it. who can write a justification and present it on a PDF?

I don't consider it nonsense. Consider this example (pulled from "The Design and Evolution of C++"):

[irrelevant example removed] [irrelevant OO discussion removed] <irrelevant> ??? this has nothing to do with the implicit conversion XX "is a" X but X is not a XX there is no confusion here, this is basic OO inheritance. and actually goes the other from the larger to the smaller. or from the specific to the general. </irrelevant>
 
Why do we have char converted to int in the first place?
what is the justification (besides historical reasons?)

Consider this expression: short s = 1; int x = s * 5; Without implicit type promotion rules the only way this would be possible is with a cast: int x = cast(int) s * 5;

that might be good. I don't know any language that strict. but in fact we are forgeting the real problem with D, consider this, where things really go wrong: ################################ class Color { void set(ubyte r,ubyte g,ubyte b) { // to set a color in GTK from 3 ubytes // some preprocessing must be executed printf("Color.set ubyte \n" ); } void set(int r,int g,int b) { printf("Color.set int \n" ); } } class ColorF : Color { void set(float r,float g,float b) { printf("ColorF.set float \n" ); } } void main() { ColorF c = new ColorF(); ubyte r = 1; ubyte g = 2; ubyte b = 3; c.set(r,g,b); } what should we expect? where's what we get: ################################# $ dmd Set.d -I~/dmd/src/phobos $ Set ColorF.set float ################################# where!, when! is that desirable? it's a bug! (0.91 clarified the method lookup resolution in the worst possible way) maybe the automatic conversion can exist on a strong type sensable language but only after the method look up with the full signature on the full class hierarchy. Ant
Sep 22 2004
parent reply Sean Kelly <sean f4.ca> writes:
In article <pan.2004.09.22.22.16.54.963216 yahoo.ca>, Ant says...
On Wed, 22 Sep 2004 21:38:02 +0000, Sean Kelly wrote:
 I don't consider it nonsense.  Consider this example (pulled from "The Design
 and Evolution of C++"):

[irrelevant example removed] [irrelevant OO discussion removed] <irrelevant> ??? this has nothing to do with the implicit conversion XX "is a" X but X is not a XX there is no confusion here, this is basic OO inheritance. and actually goes the other from the larger to the smaller. or from the specific to the general. </irrelevant>

Hardly irrelevant. If it were not for the lookup rules in D m(which mirror those in C++), your code would have worked as you intended it to. This is a two-pronged issue and I was addressing both.
but in fact we are forgeting the real problem with D,
consider this, where things really go wrong:
################################
class Color
{
	void set(ubyte r,ubyte g,ubyte b)
	{
		// to set a color in GTK from 3 ubytes // some preprocessing must be
		executed printf("Color.set ubyte \n" );
	}
	void set(int r,int g,int b)
	{
		printf("Color.set int \n" );
	}
}

class ColorF : Color
{
	void set(float r,float g,float b)
	{
		printf("ColorF.set float \n" );
	}
}

void main()
{
	ColorF c = new ColorF();
	ubyte r = 1;
	ubyte g = 2;
	ubyte b = 3;
	c.set(r,g,b);
}

what should we expect?
where's what we get:

#################################
$ dmd Set.d -I~/dmd/src/phobos
$ Set
ColorF.set float 
#################################

where!, when! is that desirable?
it's a bug!

Assuming you expected it to print "Color.set ubyte" then this is the exact issue I was explaining above which you dismissed as "irrelevant." If you expected a compile-time error, then please offer a reason why implicit type promotion should not support ubyte to float conversions. And understand that this will likely impact any expression involving a ubyte and float value, not just overload resolution rules. Sean
Sep 22 2004
parent reply Ant <duitoolkit yahoo.ca> writes:
On Wed, 22 Sep 2004 22:42:59 +0000, Sean Kelly wrote:

 In article <pan.2004.09.22.22.16.54.963216 yahoo.ca>, Ant says...
it's a bug!

Assuming you expected it to print "Color.set ubyte" then this is the exact issue I was explaining above which you dismissed as "irrelevant."

I still think it's different situations.
  If you expected a
 compile-time error,

no, I expected the funct with the same name and paramter types to be called. I'm sorry if it was not clear.
 then please offer a reason why implicit type promotion
 should not support ubyte to float conversions.

I can accept that but I have a valid visible method with ubytes. I want that to be used.
 And understand that this will
 likely impact any expression involving a ubyte and float value, not just
 overload resolution rules.  

yep, so let keep them but lookup the methods available correctly. D makes the inherited methods "second class" methods. Ant
Sep 22 2004
parent reply Regan Heath <regan netwin.co.nz> writes:
On Wed, 22 Sep 2004 20:11:14 -0400, Ant <duitoolkit yahoo.ca> wrote:
 On Wed, 22 Sep 2004 22:42:59 +0000, Sean Kelly wrote:

 In article <pan.2004.09.22.22.16.54.963216 yahoo.ca>, Ant says...
 it's a bug!

Assuming you expected it to print "Color.set ubyte" then this is the exact issue I was explaining above which you dismissed as "irrelevant."

I still think it's different situations.

It depends on how you look at it, it is a 2 part problem: - part 1, how methods are resolved. - part 2, implicit type casting. See the rules below for an explaination.
  If you expected a
 compile-time error,

no, I expected the funct with the same name and paramter types to be called. I'm sorry if it was not clear.

I understand what you expected to happen, I've also expected the same thing... that was before I was told the method resolution rules and why they are the way they are.
 then please offer a reason why implicit type promotion
 should not support ubyte to float conversions.

I can accept that but I have a valid visible method with ubytes. I want that to be used.

The crux of the matter is what 'visible' is defined as, in C/C++ and D it's not the same as in Java which I suspect has the behaviour you expect/want.
 And understand that this will
 likely impact any expression involving a ubyte and float value, not just
 overload resolution rules.

yep, so let keep them but lookup the methods available correctly. D makes the inherited methods "second class" methods.

That is one way to look at it, yes. These are the method resolution rules detailed by Walter in a previous post: digitalmars.D/6978 <quote> How name lookup happens in a scope is a multi-stage process, each proceeding to the next if the name is not found in the current stage: 1) look for a member with the name 2) look in any imports *in this scope* 3) if current scope is a class scope, recursively look in base classes 4) repeat the process with the most enclosing scope I think it would be very strange to have base class symbols override imports in the current scope, i.e. if imports were looked up in some other scope than the one in which they were imported. </quote> So... D looks for a method name that matches (not taking into consideration the parameter(s) at all) when it finds one, it tries to implicitly cast what it has to what it needs to call that method, if this fails it gives an error. Java looks for a method name that matches, and it takes into consideration the parameter(s) meaning only a method taking the right parameters can match. The reason for D's behaviour is based on C/C++ behaviour, and they reason for that was detailed by Walter... <quote> Stroustrup gives two examples (slightly modified here): --------------------------------- class X1 { void f(int); } // chain of derivations X(n) : X(n-1) class X9: X8 { void f(double); } void g(X9 p) { p.f(1); // X1.f or X9.f ? } ----------------------------------- His argument is that one can easilly miss an overload of f() somewhere in a complex class heirarchy, and argues that one should not need to understand everything about a class heirarchy in order to derive from it. The other example involves operator=(), but since D doesn't allow overloading operator=() instead I'll rewrite it as if it were a function that needs to alter a class state, and a derived class written later that 'caches' a computation on the derived state: class B { long x; void set(long i) { x = i; } void set(int i) { x = i; } long squareIt() { return x * x; } } class D : B { long square; void set(long i) { B.set(i); square = x * x; } long squareIt() { return square; } } Now, imagine B were a complex class with a lot of stuff in it, and our optimizing programmer missed the existence of set(int). Then, one has: long foo(B b) { b.set(3); return b.squareIt(); } and we have an obscure bug. </quote> "Roberto Mariottini" tried Walters example above in Java, he found.. <Roberto Mariottini> I translated your example in Java, and it shows the "wrong" (non-C++) behaviour. The attached Test.java prints: The square is: 0 </Roberto Mariottini> So basically I can understand why you want/expect what you do, but I can also understand why it doesn't work that way. I dont think there is a perfect solution to this problem, and I am happy with the current one, it appears more robust to me. Regan -- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Sep 22 2004
parent Ant <duitoolkit yahoo.ca> writes:
On Thu, 23 Sep 2004 13:21:33 +1200, Regan Heath wrote:


Ok, you convinced me that this is not going to change
I'll shut up.

Ant
Sep 22 2004