www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Compile-time reflection

reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
The subject of compile-time reflection has been an important one to me. 
I have been musing on it since about the time I started writing Pyd. 
Here is the current state of my thoughts on the matter.

----
Functions
----

When talking about functions, a given symbol may refer to multiple 
functions:

void foo() {}
void foo(int i) {}
void foo(int i, int j, int k=20) {}

The first thing a compile-time reflection mechanism needs is a way to, 
given a symbol, derive a tuple of the signatures of the function 
overloads. There is no immediately obvious syntax for this.

The is() expression has so far been the catch-all location for many of 
D's reflection capabilities. However, is() operates on types, not 
arbitrary symbols.

A property is more promising. Re-using the .tupleof property is one idea:

foo.tupleof => Tuple!(void function(), void function(int), void 
function(int, int, int))

However, I am not sure how plausible it is to have a property on a 
symbol like this. Another alternative is to have some keyword act as a 
function (as typeof and typeid do, for instance). I propose adding 
"tupleof" as an actual keyword:

tupleof(foo) => Tuple!(void function(), void function(int), void 
function(int, int, int))

I will be using this syntax throughout the rest of this post. For the 
sake of consistency, tupleof(Foo) should do what Foo.tupleof does now.

To umabiguously refer to a specific overload of a function, two pieces 
of information are required: The function's symbol, and the signature of 
the overload. When doing compile-time reflection, one is typically 
working with one specific overload at a time. While a function pointer 
does refer to one specific overload, it is important to note that 
function pointers are not compile-time entities! Therefore, the 
following idiom is common:

template UseFunction(alias func, func_t) {}

That is, any given template that does something with a function requires 
both the function's symbol and the signature of the particular overload 
to operate on to be useful.

It should be clear, then, that automatically deriving the overloads of a 
given function is very important. Another piece of information that is 
useful is whether a given function has default arguments, and how many. 
The tupleof() syntax can be re-used for this:

tupleof(foo, void function(int, int, int)) => Tuple!(void function(int, 
int))

Here, we pass tupleof() the symbol of a function, and the signature of a 
particular overload of that function. The result is a tuple of the 
various signatures it is valid to call the overload with, ignoring the 
/actual/ signature of the function. The most useful piece of information 
here is the /number/ of elements in the tuple, which will be equal to 
the number of default arguments supported by the overload.

One might be tempted to place these additional function signatures in 
the original tuple derived by tupleof(foo). However, this is not 
desirable. Consider: We can say any of the following:

void function() fn1 = &foo;
void function(int) fn2 = &foo;
void function(int, int, int) fn3 = &foo;

But we /cannot/ say this:

void function(int, int) fn4 = &foo; // ERROR!

A given function-symbol therefore has two sets of function signatures 
associated with it: The actual signatures of the functions, and the 
additional signatures it may be called with due to default arguments. 
These two sets are not equal in status, and should not be treated as such.

----
Member functions
----

Here is where things get really complicated.

class A {
     void bar() {}
     void bar(int i) {}
     void bar(int i, int j, int k=20) {}

     void baz(real r) {}

     static void foobar() {}
     final void foobaz() {}
}

class B : A {
     void foo() {}
     override void baz(real r) {}
}

D does not really have pointers to member functions. It is possible to 
fake them with some delegate trickery. In particular, there is no way to 
directly call an alias of a member function. This is important, as I 
will get to later.

The first mechanism needed is a way to get all of the member functions 
of a class. I suggest the addition of a .methodsof class property, which 
will derive a tuple of aliases of the class's member functions.

A.methodsof => Tuple!(A.bar, A.baz, A.foobar, A.foobaz)
B.methodsof => Tuple!(A.bar, A.foobar, A.foobaz, B.foo, B.baz)

The order of the members in this tuple is not important. Inherited 
member functions are included, as well. Note that these are tuples of 
symbol aliases! Since these are function symbols, all of the mechanisms 
suggested earlier for regular function symbols should still work!

tupleof(A.bar) => Tuple!(void function(), void function(int), void 
function(int, int, int))

And so forth.

There are three kinds of member functions: virtual, static, and final. 
The next important mechanism that is needed is a way to distinguish 
these from each other. An important rule of function overloading works 
in our favor, here: A given function symbol can only refer to functions 
which are all virtual, all static, or all final. Therefore, this should 
be considered a property of the symbol, as opposed to one of the 
function itself.

The actual syntax for this mechanism needs to be determined. D has 
'static' and 'final' keywords, but no 'virtual' keyword. Additionally, 
the 'static' keyword has been overloaded with many meanings, and I 
hesitate suggesting we add another. Nonetheless, I do.

static(A.bar == static) == false
static(A.bar == final) == false
static(A.bar == virtual) == true

The syntax is derived from that of the is() expression. The grammar 
would look something like this:

StaticExpression:
	static ( Symbol == SymbolSpecialization )

SymbolSpecialization:
	static
	final
	virtual

Here, 'virtual' is a context-sensitive keyword, not unlike the 'exit' in 
'scope(exit)'. If the Symbol is not a member function, it is an error.

A hole presents itself in this scheme. We can get all of the function 
symbols of a class's member functions. From these, we can get the 
signatures of their overloads. From /these/, can get get pointers to the 
member functions, do some delegate trickery, and actually call them. 
This is all well and good.

But there is a problem when a method has default arguments. As explained 
earlier, we can't do this:

// Error! None of the overloads match!
void function(int, int) member_func = &A.bar;

Even though we can say:

A a = new A;
a.bar(1, 2);

The simplest solution is to introduce some way to call an alias of a 
method directly. There are a few options. My favorite is to take a cue 
from Python, and allow the following:

alias A.bar fn;
A a = new A;
fn(a, 1, 2);

That is, allow the user to explicitly call the method with the instance 
as the first parameter. This should be allowed generally, as in:

A.bar(a);
A.baz(a, 5.5);

Given these mechanisms, combined with the existing mechanisms to derive 
the return type and parameter type tuple from a function type, D's 
compile-time reflection capabilities would be vastly more powerful.

-- 
Kirk McDonald
http://kirkmcdonald.blogspot.com
Pyd: Connecting D and Python
http://pyd.dsource.org
Jul 01 2007
next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Kirk McDonald wrote:
<snip>
 The first thing a compile-time reflection mechanism needs is a way to, 
 given a symbol, derive a tuple of the signatures of the function 
 overloads. There is no immediately obvious syntax for this.
Overloads are entirely separate functions. If you go through ClassInfo.vtbl, that gives you each overload separately. And the only thing overloads have in common are their names. So is this actually important? The annoyance is when you have overloads, how do you refer to the correct one? --- void foo(int i) {} void foo(int i, char[] str) {} auto f1 = &foo; // works, but which does it give? void function(int, char[]) f2 = &foo; // does the right thing --- That works fine, as long as one person wrote it all specifically for the cases it's used in. What if you're sending a function reference to some other piece of code that can take any function? --- void foo(int i) {} void foo(int i, char[] str) {} some_other_lib.use_some_function(&foo); --- Well, you can do the obvious workaround: --- void function(int) foo_to_send = &foo; some_other_lib.use_some_function(&foo_to_send); --- It just bothers me that the only way to specify which reference is by assignment to a function reference with a specific signature. I'd prefer a syntax more like: auto f1 = &foo(int, char[]); Unambiguous, since references don't have an opCall, and shorter. But the present workaround is merely annoying, and only mildly at that. <snip>
 foo.tupleof => Tuple!(void function(), void function(int), void 
 function(int, int, int))
Okay, sounds simple enough, but why do you need each overload? They're separate functions; they don't have anything in common, strictly speaking, except the name.
 It should be clear, then, that automatically deriving the overloads of a 
 given function is very important. Another piece of information that is 
 useful is whether a given function has default arguments, and how many. 
 The tupleof() syntax can be re-used for this:
 
 tupleof(foo, void function(int, int, int)) => Tuple!(void function(int, 
 int))
This is interesting. Currently, there's no way to get which arguments are omissible. ...
 D does not really have pointers to member functions. It is possible to 
 fake them with some delegate trickery. In particular, there is no way to 
 directly call an alias of a member function. This is important, as I 
 will get to later.
The reason for this is obvious; the compiler rewrites the functions as you describe below. I haven't looked, but I'd guess a delegate is something like this: struct delegate (T, R, U...) { void* func; T obj; R opCall(U u) { return *func(obj, u); } } ...
 A.methodsof => Tuple!(A.bar, A.baz, A.foobar, A.foobaz)
 B.methodsof => Tuple!(A.bar, A.foobar, A.foobaz, B.foo, B.baz)
You can already get this through A.classinfo.vtbl. That contains all functions that are valid for the class. You can create a template that will determine which functions are inherited, though you cannot say which override functions from the base class with any certainty.
 static(A.bar == static) == false
 static(A.bar == final) == false
 static(A.bar == virtual) == true
Currently, there's no way I know of to get this information. The is(typeof()) system works with all functions, static or not, whether you use them from a type or from an instance. Hack? Do everything from instance variables. Dunno what to do about final, though. Checking about final stuff matters if you want your program to behave differently toward final methods, but nothing in ClassInfo prevents you from replacing a final method.
Jul 01 2007
next sibling parent Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Christopher Wright wrote:
 Kirk McDonald wrote:
 <snip>
 
 The first thing a compile-time reflection mechanism needs is a way to, 
 given a symbol, derive a tuple of the signatures of the function 
 overloads. There is no immediately obvious syntax for this.
Overloads are entirely separate functions. If you go through ClassInfo.vtbl, that gives you each overload separately. And the only thing overloads have in common are their names. So is this actually important?
I am interested in compile-time reflection. The vtable doesn't exist at compile-time. The only way to refer to a function at compile-time is through a combination of the symbol name and the signature. Anything involving function pointers is necessarily a runtime operation. [snip]
 <snip>
 
 foo.tupleof => Tuple!(void function(), void function(int), void 
 function(int, int, int))
Okay, sounds simple enough, but why do you need each overload? They're separate functions; they don't have anything in common, strictly speaking, except the name.
Same reason: It is a strictly compile-time operation.
 It should be clear, then, that automatically deriving the overloads of 
 a given function is very important. Another piece of information that 
 is useful is whether a given function has default arguments, and how 
 many. The tupleof() syntax can be re-used for this:

 tupleof(foo, void function(int, int, int)) => Tuple!(void 
 function(int, int))
This is interesting. Currently, there's no way to get which arguments are omissible.
Not entirely true; see std.bind.minNumArgs (which is a variadic version, by h3r3tic, of a non-variadic template I wrote before we had tuples). I am simply proposing making this a language feature, rather than the hack it currently is.
 ...
 
 D does not really have pointers to member functions. It is possible to 
 fake them with some delegate trickery. In particular, there is no way 
 to directly call an alias of a member function. This is important, as 
 I will get to later.
The reason for this is obvious; the compiler rewrites the functions as you describe below. I haven't looked, but I'd guess a delegate is something like this: struct delegate (T, R, U...) { void* func; T obj; R opCall(U u) { return *func(obj, u); } } ...
The ABI is different; the instance is passed in a register, or somesuch.
 A.methodsof => Tuple!(A.bar, A.baz, A.foobar, A.foobaz)
 B.methodsof => Tuple!(A.bar, A.foobar, A.foobaz, B.foo, B.baz)
You can already get this through A.classinfo.vtbl. That contains all functions that are valid for the class. You can create a template that will determine which functions are inherited, though you cannot say which override functions from the base class with any certainty.
Again, the vtable is not compile-time information.
 static(A.bar == static) == false
 static(A.bar == final) == false
 static(A.bar == virtual) == true
Currently, there's no way I know of to get this information. The is(typeof()) system works with all functions, static or not, whether you use them from a type or from an instance. Hack? Do everything from instance variables. Dunno what to do about final, though. Checking about final stuff matters if you want your program to behave differently toward final methods, but nothing in ClassInfo prevents you from replacing a final method.
My interest is in improving Pyd. Given a class, I want to generate a Python type wrapping the entirety of that class, as well as a D subclass of the type, providing virtual dispatching to the Python type. This means I effectively have to know /everything/ about the class and its methods, at compile-time. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jul 01 2007
prev sibling parent reply Fawzi Mohamed <fmohamed mac.com> writes:
I understand the need of reflection t connect D to other languages.
Still I think that a solution to this problem should be (not 
necessarily now, but in the future) extensible to template functions.

The virtual/final/static attributes seem quite straightforward and 
unproblematic (I like the is(x==virtual) solution because it is what I 
would have expected).
The symbol+signature -> function pointer is also unproblematic (be it 
through an alias cast or assignment).
The symbol -> signatures thing can be problematic if templates enter in 
the picture.

One could think of representing all kind of signatures by allowing things like
template(TemplateType T0){template(alias T1){template(int 
T2){template(int T3){T0 function(T0[T2],optional T0[T3])}
or something like it.
Unfortunately this is not enough as there is no guarantee that a given 
template is instantiable.
Instantiability can be controlled at compile time, but still for 
automatic interface generators one cannot avoid giving them some extra 
information.

One could think about giving restricting the list of signatures either 
to full specializations (and maybe have some hint that extra ones might 
exist), or to the templates that have been instantiated (but such a set 
is not fixed and can change from program to program).

In any case some thought should be spent about this.

Fawzi
May 31 2008
parent lihong <service.pvpsale gmail.com> writes:
We offer World of Warcraft Power Leveling and World of Warcraft
powerleveling,Final Fantasy XI Gil,Maple Story and Lord fo Ring Online
Powerleveling.WoW Powerleveling service and cheap wow Powerleveling,World of
Warcraft Power leveling sale for you. WOW Power leveling,Cheap WOW
Powerleveling 1-60 and Cheap WOW Powerleveling 1 - 70.All service is
faster,safer,and cheaper.WOW Powerleveling 1 - 60,WOW Powerleveling 1 - 70.Lord
fo Ring Online Power Leveling 1-50 .We Please remember,we are your online game
helper.pvpsale
May 31 2008
prev sibling next sibling parent reply BCS <ao pathlink.com> writes:
Reply to Kirk,

 void foo() {}
 void foo(int i) {}
 void foo(int i, int j, int k=20) {}
 The first thing a compile-time reflection mechanism needs is a way to,
 given a symbol, derive a tuple of the signatures of the function
 overloads. 
[...]
 
 foo.tupleof => Tuple!(void function(), void function(int), void
 function(int, int, int))
 
Why have this give a type tuple? I'd have it give alias to the overloads them selves.
Jul 02 2007
parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
BCS wrote:
 Reply to Kirk,
 
 void foo() {}
 void foo(int i) {}
 void foo(int i, int j, int k=20) {}
 The first thing a compile-time reflection mechanism needs is a way to,
 given a symbol, derive a tuple of the signatures of the function
 overloads. 
[...]
 foo.tupleof => Tuple!(void function(), void function(int), void
 function(int, int, int))
Why have this give a type tuple? I'd have it give alias to the overloads them selves.
Aliases operate on symbols. All of those functions have the same symbol. An alias to a specific overload is nonsensical. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jul 02 2007
parent BCS <ao pathlink.com> writes:
Reply to Kirk,

 BCS wrote:
 
 Reply to Kirk,
 
 void foo() {}
 void foo(int i) {}
 void foo(int i, int j, int k=20) {}
 The first thing a compile-time reflection mechanism needs is a way
 to,
 given a symbol, derive a tuple of the signatures of the function
 overloads.
[...]
 foo.tupleof => Tuple!(void function(), void function(int), void
 function(int, int, int))
 
Why have this give a type tuple? I'd have it give alias to the overloads them selves.
Aliases operate on symbols. All of those functions have the same symbol. An alias to a specific overload is nonsensical.
I would clam that an alias to a specific overload being nonsensical should in it's self be nonsensical. Why shouldn't I be able to get an alias to a specific function even if it is overloaded? I would propose that an unqualified function name (or an alias of it) would be the set of overloads (or alias to them) and a qualified name would be a specific function. I would be interested in way this would be a bad idea. void foo(int i){} void foo(Object i){} alias foo bar; // bar == {foo(int) and foo(Object)} int i; bar(i); // works bar(null); // works // proposed syntax for overload resolution alias foo(Object) baz; baz(i); // fails int i can't be converted to Object baz(null); // works
Jul 03 2007
prev sibling next sibling parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Kirk McDonald wrote:
<snip>

It would be great to have the functionality you propose in D.

 
 There are three kinds of member functions: virtual, static, and final. 
 The next important mechanism that is needed is a way to distinguish 
 these from each other. An important rule of function overloading works 
 in our favor, here: A given function symbol can only refer to functions 
 which are all virtual, all static, or all final. Therefore, this should 
 be considered a property of the symbol, as opposed to one of the 
 function itself.
 
 The actual syntax for this mechanism needs to be determined. D has 
 'static' and 'final' keywords, but no 'virtual' keyword. Additionally, 
 the 'static' keyword has been overloaded with many meanings, and I 
 hesitate suggesting we add another. Nonetheless, I do.
 
 static(A.bar == static) == false
 static(A.bar == final) == false
 static(A.bar == virtual) == true
This looks very confusing to me, however, if you'd replace the first static with 'is' it makes sense: is(A.bar == static) == false is(A.bar == final) == false is(A.bar == virtual) == true Is there a problem with this, grammar-wise? Btw, if introducing 'virtual' is undesirable, it can be left out since it can be inferred from being final nor static, and for this some library side syntactic sugar can be made.
Jul 02 2007
parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Lutger wrote:
 Kirk McDonald wrote:
 <snip>
 
 It would be great to have the functionality you propose in D.
 
 There are three kinds of member functions: virtual, static, and final. 
 The next important mechanism that is needed is a way to distinguish 
 these from each other. An important rule of function overloading works 
 in our favor, here: A given function symbol can only refer to 
 functions which are all virtual, all static, or all final. Therefore, 
 this should be considered a property of the symbol, as opposed to one 
 of the function itself.

 The actual syntax for this mechanism needs to be determined. D has 
 'static' and 'final' keywords, but no 'virtual' keyword. Additionally, 
 the 'static' keyword has been overloaded with many meanings, and I 
 hesitate suggesting we add another. Nonetheless, I do.

 static(A.bar == static) == false
 static(A.bar == final) == false
 static(A.bar == virtual) == true
This looks very confusing to me, however, if you'd replace the first static with 'is' it makes sense: is(A.bar == static) == false is(A.bar == final) == false is(A.bar == virtual) == true Is there a problem with this, grammar-wise? Btw, if introducing 'virtual' is undesirable, it can be left out since it can be inferred from being final nor static, and for this some library side syntactic sugar can be made.
Grammar wise, using 'virtual' would cause some problems. (What if you have a type called 'virtual' and want to compare some other type to it?) There is another, more serious, problem: is() is defined such that if the first thing passed to it isn't a type, it returns false. (That is, one of its purposes is to test if something is a valid type.) I think that making an exception to this rule for 'static' and 'final' could potentially be confusing. Therefore, some other keyword is required. I picked 'static' more or less arbitrarily. It also allows the use of 'virtual' directly. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jul 02 2007
parent Lutger <lutger.blijdestijn gmail.com> writes:
Kirk McDonald wrote:
...
 This looks very confusing to me, however, if you'd replace the first 
 static with 'is' it makes sense:

 is(A.bar == static) == false
 is(A.bar == final) == false
 is(A.bar == virtual) == true

 Is there a problem with this, grammar-wise?

 Btw, if introducing 'virtual' is undesirable, it can be left out since 
 it can be inferred from being final nor static, and for this some 
 library side syntactic sugar can be made.
Grammar wise, using 'virtual' would cause some problems. (What if you have a type called 'virtual' and want to compare some other type to it?) There is another, more serious, problem: is() is defined such that if the first thing passed to it isn't a type, it returns false. (That is, one of its purposes is to test if something is a valid type.) I think that making an exception to this rule for 'static' and 'final' could potentially be confusing. Therefore, some other keyword is required. I picked 'static' more or less arbitrarily. It also allows the use of 'virtual' directly.
I see. Well, it's not that important, the big thing is if the functionality will be included. Still I would argue that it is preferable to use 'is', because it's both clearer and more consistent. The problem with is() returning false when the symbol tested isn't a type occurs now too, I don't understand how this is different from current cases with static / final / virtual? If we need to hose virtual, one would have to write: !(is(A.bar == static) || is(A.bar == final)) This is less than ideal, but I can live with that for the benefit it gives. In practice I would wrap it anyway in some traits module.
Jul 02 2007
prev sibling next sibling parent reply Gregor Richards <Richards codu.org> writes:
Time for a response without reading the entire message 8-D

http://www.dsource.org/projects/tango.tools/browser/trunk/tools/rodin

(FYI: Rodin will work with either Tango or Phobos)

  - Gregor Richards
Jul 03 2007
parent reply =?UTF-8?B?SnVsaW8gQ8Opc2FyIENhcnJhc2NhbCBVcnF1aWpv?= writes:
Gregor Richards wrote:
 Time for a response without reading the entire message 8-D
 
 http://www.dsource.org/projects/tango.tools/browser/trunk/tools/rodin
 
 (FYI: Rodin will work with either Tango or Phobos)
 
  - Gregor Richards
Very impressive Gregor. One question: Will it be possible to call a function without knowing it's signature at compile time? In all examples you used a delegate, a function or a base class. What I'd like to do is something like: object obj = refGetClass(classPath); object[] arguments = new object[] { 1, "string", new Object() }; refCallMethod(obj, "foo", arguments); Obviously the object[] doesn't work on D but maybe Box(T)? Thanks
Jul 03 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Julio César Carrascal Urquijo wrote:
 Gregor Richards wrote:
 Time for a response without reading the entire message 8-D

 http://www.dsource.org/projects/tango.tools/browser/trunk/tools/rodin

 (FYI: Rodin will work with either Tango or Phobos)

  - Gregor Richards
Very impressive Gregor.
Can someone post a synopsis for those of us too lazy to go sifting through an svn repository? --bb
Jul 03 2007
parent Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Bill Baxter wrote:
 Julio César Carrascal Urquijo wrote:
 Gregor Richards wrote:
 Time for a response without reading the entire message 8-D

 http://www.dsource.org/projects/tango.tools/browser/trunk/tools/rodin

 (FYI: Rodin will work with either Tango or Phobos)

  - Gregor Richards
Very impressive Gregor.
Can someone post a synopsis for those of us too lazy to go sifting through an svn repository? --bb
It is a project in two parts: drefgen is a utility, based on the DMD front-end and written in C++, which generates meta-data about whatever code you pass to it. rodin is a D library which is capable of reading that meta-data, allowing client code (quite possibly the very code you generated the meta-data from) access to it. While it is an impressive piece of work, the resulting data is not available at compile-time, just run-time. Therefore it is not quite what my post was talking about. It might be able to get there with some work, but I have an aversion to code generation. Actual support in the compiler would be vastly preferable. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jul 03 2007
prev sibling next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Gregor Richards Wrote:

 Time for a response without reading the entire message 8-D
 
 http://www.dsource.org/projects/tango.tools/browser/trunk/tools/rodin
 
 (FYI: Rodin will work with either Tango or Phobos)
 
   - Gregor Richards
Wow, I had no idea that even existed... what other hidden open-source D projects are there?
Jul 03 2007
parent Giles Bathgate <gilesbathgate gmail.com> writes:
 Wow, I had no idea that even existed... what other hidden open-source D
projects are there?
surprisingly alot more than you would expect. The trouble I find is that they are all a bit scattered here and there. It would be nice if there was a "distribution" of D, phobos, and a set of decent libraries that all fit together kind of like a Framework. Then again that takes the fun out of hunting them down ;)
Jul 04 2007
prev sibling next sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Kirk McDonald wrote:
 
 ----
 Member functions
 ----
 
 Here is where things get really complicated.
 
 class A {
     void bar() {}
     void bar(int i) {}
     void bar(int i, int j, int k=20) {}
 
     void baz(real r) {}
 
     static void foobar() {}
     final void foobaz() {}
 }
 
 class B : A {
     void foo() {}
     override void baz(real r) {}
 }
 
 D does not really have pointers to member functions. It is possible to 
 fake them with some delegate trickery. In particular, there is no way to 
 directly call an alias of a member function. This is important, as I 
 will get to later.
 
 The first mechanism needed is a way to get all of the member functions 
 of a class. I suggest the addition of a .methodsof class property, which 
 will derive a tuple of aliases of the class's member functions.
 
 A.methodsof => Tuple!(A.bar, A.baz, A.foobar, A.foobaz)
 B.methodsof => Tuple!(A.bar, A.foobar, A.foobaz, B.foo, B.baz)
 
 The order of the members in this tuple is not important. Inherited 
 member functions are included, as well. Note that these are tuples of 
 symbol aliases! Since these are function symbols, all of the mechanisms 
 suggested earlier for regular function symbols should still work!
 
 tupleof(A.bar) => Tuple!(void function(), void function(int), void 
 function(int, int, int))
 
 And so forth.
 
 There are three kinds of member functions: virtual, static, and final. 
 The next important mechanism that is needed is a way to distinguish 
 these from each other. An important rule of function overloading works 
 in our favor, here: A given function symbol can only refer to functions 
 which are all virtual, all static, or all final. Therefore, this should 
 be considered a property of the symbol, as opposed to one of the 
 function itself.
 
Why would we need to distinguish between virtual, static and final member functions?
 
 Given these mechanisms, combined with the existing mechanisms to derive 
 the return type and parameter type tuple from a function type, D's 
 compile-time reflection capabilities would be vastly more powerful.
 
I'm not sure about some of the detail, but in a general wey, yes it would be welcome to have such changes that would allows us to better work with functions. Some of the issues here are not just about compile time reflection, for instance, that you have to create a temporary type, or do a cast, to get a function pointer of a particular overload, seems quite hackish to me. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jul 08 2007
next sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Bruno Medeiros" <brunodomedeiros+spam com.gmail> wrote in message 
news:f6qiqg$1bsg$1 digitalmars.com...
 Why would we need to distinguish between virtual, static and final member 
 functions?
At least from a scripting-language-binding-library point of view, it's useful to know that so that when you write: Def!(A.foo); Def!(A.bar); Def!(A.baz); If foo is static, bar is virtual, and baz is final, they might all have to be bound in different ways.
Jul 08 2007
prev sibling next sibling parent reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Bruno Medeiros wrote:
 Kirk McDonald wrote:
 There are three kinds of member functions: virtual, static, and final. 
 The next important mechanism that is needed is a way to distinguish 
 these from each other. An important rule of function overloading works 
 in our favor, here: A given function symbol can only refer to 
 functions which are all virtual, all static, or all final. Therefore, 
 this should be considered a property of the symbol, as opposed to one 
 of the function itself.
Why would we need to distinguish between virtual, static and final member functions?
For starters, you can directly call static member functions, but not the others (they require an instance). The difference between virtual and final is more subtle, but is still worth-while to know if you start doing some stupid inheritance tricks. As Jarrett points out, this information is particularly valuable when binding the language to another language.
 Given these mechanisms, combined with the existing mechanisms to 
 derive the return type and parameter type tuple from a function type, 
 D's compile-time reflection capabilities would be vastly more powerful.
I'm not sure about some of the detail, but in a general wey, yes it would be welcome to have such changes that would allows us to better work with functions. Some of the issues here are not just about compile time reflection, for instance, that you have to create a temporary type, or do a cast, to get a function pointer of a particular overload, seems quite hackish to me.
This would not change that. In fact, I honestly see nothing wrong with that. Casting seems like simplest way of specifying the overload, without introducing new syntax. (Although I'm aware some folks want to introduce new syntax for this express purpose.) -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jul 08 2007
parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Kirk McDonald wrote:
 I'm not sure about some of the detail, but in a general wey, yes it 
 would be welcome to have such changes that would allows us to better 
 work with functions. Some of the issues here are not just about 
 compile time reflection, for instance, that you have to create a 
 temporary type, or do a cast, to get a function pointer of a 
 particular overload, seems quite hackish to me.
This would not change that. In fact, I honestly see nothing wrong with that. Casting seems like simplest way of specifying the overload, without introducing new syntax. (Although I'm aware some folks want to introduce new syntax for this express purpose.)
Casting is a kludge, and should not be used for something that is perfectly normal, valid (and possibly common). Fortunately we can hide it with some cleaner templates: auto fn2 = overload!(fn, int, char) // select fn(int, char) overload I don't think new syntax should be added. But that doesn't mean that some things could not be improved with the current design. For instance, taking the address of an overloaded function without a cast (or another overload selection mechanism) should result in a error, instead of simply returning the address of the lexically first overload. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jul 11 2007
parent Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Bruno Medeiros wrote:
 Kirk McDonald wrote:
 
 I'm not sure about some of the detail, but in a general wey, yes it 
 would be welcome to have such changes that would allows us to better 
 work with functions. Some of the issues here are not just about 
 compile time reflection, for instance, that you have to create a 
 temporary type, or do a cast, to get a function pointer of a 
 particular overload, seems quite hackish to me.
This would not change that. In fact, I honestly see nothing wrong with that. Casting seems like simplest way of specifying the overload, without introducing new syntax. (Although I'm aware some folks want to introduce new syntax for this express purpose.)
Casting is a kludge, and should not be used for something that is perfectly normal, valid (and possibly common). Fortunately we can hide it with some cleaner templates: auto fn2 = overload!(fn, int, char) // select fn(int, char) overload I don't think new syntax should be added. But that doesn't mean that some things could not be improved with the current design. For instance, taking the address of an overloaded function without a cast (or another overload selection mechanism) should result in a error, instead of simply returning the address of the lexically first overload.
I agree with making that an error, so long as we get a way to automatically derive the types of all of the overloads. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jul 11 2007
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Bruno Medeiros wrote:
 Kirk McDonald wrote:
 ----
 Member functions
 ----

 Here is where things get really complicated.

 class A {
     void bar() {}
     void bar(int i) {}
     void bar(int i, int j, int k=20) {}

     void baz(real r) {}

     static void foobar() {}
     final void foobaz() {}
 }

 class B : A {
     void foo() {}
     override void baz(real r) {}
 }

 D does not really have pointers to member functions. It is possible to 
 fake them with some delegate trickery. In particular, there is no way 
 to directly call an alias of a member function. This is important, as 
 I will get to later.

 The first mechanism needed is a way to get all of the member functions 
 of a class. I suggest the addition of a .methodsof class property, 
 which will derive a tuple of aliases of the class's member functions.

 A.methodsof => Tuple!(A.bar, A.baz, A.foobar, A.foobaz)
 B.methodsof => Tuple!(A.bar, A.foobar, A.foobaz, B.foo, B.baz)

 The order of the members in this tuple is not important. Inherited 
 member functions are included, as well. Note that these are tuples of 
 symbol aliases! Since these are function symbols, all of the 
 mechanisms suggested earlier for regular function symbols should still 
 work!

 tupleof(A.bar) => Tuple!(void function(), void function(int), void 
 function(int, int, int))

 And so forth.

 There are three kinds of member functions: virtual, static, and final. 
 The next important mechanism that is needed is a way to distinguish 
 these from each other. An important rule of function overloading works 
 in our favor, here: A given function symbol can only refer to 
 functions which are all virtual, all static, or all final. Therefore, 
 this should be considered a property of the symbol, as opposed to one 
 of the function itself.
Why would we need to distinguish between virtual, static and final member functions?
Ok, I don't think I made my point correctly. Yes, it's useful to distinguish between virtual, static and final member functions[*], but that's something that could be done (if not already) with is-expressions. What I mean is more about this: "An important rule of function overloading works in our favor, here: A given function symbol can only refer to functions which are all virtual, all static, or all final." Why would we want this change? [*] Although, I'm not so sure about virtual vs. final distinctions. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jul 11 2007
parent Kirk McDonald <kirklin.mcdonald gmail.com> writes:
Bruno Medeiros wrote:
 Ok, I don't think I made my point correctly. Yes, it's useful to 
 distinguish between virtual, static and final member functions[*], but 
 that's something that could be done (if not already) with 
 is-expressions. What I mean is more about this:
 "An important rule of function overloading works in our favor, here: A 
 given function symbol can only refer to functions which are all virtual, 
 all static, or all final."
 Why would we want this change?
 
I had somehow been mistaken that this was the way it worked currently. Whoops. Since this isn't true, the StaticExpression syntax would have to specify the particular overload to operate on, something like: StaticExpression: static ( Symbol Type == SymbolSpecialization ) Where Type is the signature of the overload to operate on, as in: class C { void foo() {} static void foo(int i) {} } static(C.foo void function() == virtual) // true static(C.foo void function(int) == static) // true Specifying an non-existent overload should probably be an error.
 
 [*] Although, I'm not so sure about virtual vs. final distinctions.
 
As I said, this distintion may be important if you're doing certain tricks with inheritance. And if these mechanisms are being added, I don't think there's any reason /not/ to add it. -- Kirk McDonald http://kirkmcdonald.blogspot.com Pyd: Connecting D and Python http://pyd.dsource.org
Jul 11 2007
prev sibling parent Walter Bright <newshound1 digitalmars.com> writes:
Kirk McDonald wrote:
 Given these mechanisms, combined with the existing mechanisms to derive 
 the return type and parameter type tuple from a function type, D's 
 compile-time reflection capabilities would be vastly more powerful.
Thanks for taking the time to put together an especially valuable post.
Jul 08 2007