www.digitalmars.com         C & C++   DMDScript  

D - Thoughts on how to unify (and extend) functions and delegates

reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
I am making heavy use of delegates in a D program I am currently 
writing.  They are awesome!

However, more than once I have found myself wishing that they could 
store more than just one argument.  Why can't you have delegates of any 
size?  Better yet (for the needs of my program) why can't you have 
delegates which have a VARIABLE number of implicit arguments?

I think I know (roughly) how to do this.



Stated roughly, a delegate is really just a struct.  For example, this:
	int delegate(char a,Object b) foo;
could be viewed as this struct:
	struct myDelegate {
		void *arg;
		int function(void*,char,Object) fptr;
	};
	myDelegate foo;

When you call the delegate
	foo('a',null);
the compiler is basically doing this:
	foo.fptr(foo.arg, 'a',null);



So a delegate is really just a function pointer with a single void* 
argment embedded into it.  What if we decided to view this as a 
static-sized array instead?
	struct myDelegate {
		void*[1] arg;
		int function(void*,char,Object) fptr;
	};

This implies that you might have arrays of other sizes:
	struct myDelegate_2args {
		void*[2] args;
		int function(void*,void*,char,Object) fptr;
	};

In fact, a "normal" function pointer is just the trivial case, where the 
size of the array is 0:
	struct myDelegate_0args {
		void*[0] args;
		int function(char,Object) fptr;
	};

Finally, this conception suggests that we might be able to declare 
delegates with a dynamic number of arguments:
	struct myDelegate_dynArgs {
		void*[] args;
		int function(..., char,Object) fptr;
	};

In this last case, what the compiler would turn this call:
	myDelegate_dynArgs foo;
	foo('a',null);
into something like this pseudocode:
	foreach(void *arg; foo.args)
		push(arg);
	push('a');
	push(null);
	call(foo.fptr);



So I propose the following syntaxes:
	<retType> function[<val>](<args...>)
	<retType> function[](<args...>)

'function[<val>]' would be a function with <val> stored arguments.
'function' would be syntax sugar for 'function[0]'.
'delegate' would be syntax sugar for 'function[1]'.
'function[]' declares a function pointer which has a dynamic number of 
stored arguments.

You can implicitly cast any function[x] to function[] if the args and 
return values are the same.

PROBLEMS:
- What if one of the arguments you want to store is less than the size 
of a void pointer, like a char?  Maybe the [] size should be in bytes 
rather than void pointers?
- You should be able to turn a function[x] into a function[x+n] by 
supplying some additional stored arguments.  I don't have a good, 
readable syntax for this yet.  Any ideas?
Feb 27 2004
parent reply "Ben Hinkle" <bhinkle4 juno.com> writes:
"Russ Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
news:c1oiko$hs4$1 digitaldaemon.com...
| I am making heavy use of delegates in a D program I am currently
| writing.  They are awesome!
|
| However, more than once I have found myself wishing that they could
| store more than just one argument.  Why can't you have delegates of any
| size?  Better yet (for the needs of my program) why can't you have
| delegates which have a VARIABLE number of implicit arguments?

Below is some template magic to do roughly the same thing. The
calls in the user code could most likely be cleaned up but it
works just fine. Another drawback compared to your proposal is
that it allocates another wrapper object per implicit argument
so it isn't as efficient as it could be. Also it needs to support
delegates with return values. There seems to be a bug in dmd with
opApply and template classes (the error message is that wrapper(8)
expects a function and not WrapDG).
Anyway, it's fun to play around with:


// no explicit arg
class WrapDG(DG,S)
{
   S data;
   DG dg;
   this(DG dg, S data)
   {
      this.data = data;
      this.dg = dg;
   }
   void opApply()
   {
      dg(data);
   }
}
// one explicit arg
class WrapDG(DG,S,A)
{
   S data;
   DG dg;
   this(DG dg, S data)
   {
      this.data = data;
      this.dg = dg;
   }
   void opApply(A x)
   {
      dg(data,x);
   }
}


class Foo {
   int x;
   void the_callback(int p,int q)
   {
      printf("field x = %d\n",x);
      printf("param p = %d\n",p);
      printf("param q = %d\n\n",q);
   }
}

int main()
{
   Foo t = new Foo();
   t.x = 2;

   // wrap dg_t to get dg2_t
   alias void delegate(int,int) dg_t;
   alias void delegate(int) dg2_t;

   // make wrapper delegate resulting in dg
   alias WrapDG!(dg_t,int,int) wrapper_t;
   wrapper_t wrapper = new wrapper_t(&t.the_callback,4);
   dg2_t dg = &wrapper.opApply; // template opApply needs help

   // try applying it
   printf("calling delegate dg with arg 8\n");
   dg(8);

   // another example: wrap the wrapper to add more implicit args
   WrapDG!(dg2_t,int) wrapper2 = new WrapDG!(dg2_t,int)(dg,16);
   void delegate() dg2 = &wrapper2.opApply;

   // try applying it
   printf("calling delegate dg2 with no args\n");
   dg2();

   return 0;
}
Feb 28 2004
parent SpookyET <not4_u hotmail.com> writes:
Delegates and function pointers should be united in one way to point to  
functions.  The problem right now is that delegates can't call static  
methodes in a class.  Also delegates can't point to more than one method  
and you have to use arrays.

int (*pt2Function)        (float, char, char);           // C
int (TMyClass::*pt2Member)(float, char, char);           // C++


----------------------------------------------------------------------------
import std.c.stdio;

class Foobar
{
	void printHello()
	{
		puts("Foobar.printHello(): Hello  !");
	}
}

class Foobar2
{
	void printHello()
	{
		puts("Foobar2.printHello(): Hello  !");
	}
}


void main ( char [] [] args )
{
	void delegate()[2] d;
	
	Foobar f = new Foobar();
	Foobar2 f2 = new Foobar2();
	
	d[0] = &f.printHello;
	d[1] = &f2.printHello;
	
	for (int i = 0; i < d.length; i++)
	{
		d[i]();
	}
	
	foreach (void delegate() x; d)
	{
		x();
	}
}


On Sat, 28 Feb 2004 09:50:46 -0500, Ben Hinkle <bhinkle4 juno.com> wrote:

 "Russ Lewis" <spamhole-2001-07-16 deming-os.org> wrote in message
 news:c1oiko$hs4$1 digitaldaemon.com...
 | I am making heavy use of delegates in a D program I am currently
 | writing.  They are awesome!
 |
 | However, more than once I have found myself wishing that they could
 | store more than just one argument.  Why can't you have delegates of any
 | size?  Better yet (for the needs of my program) why can't you have
 | delegates which have a VARIABLE number of implicit arguments?

 Below is some template magic to do roughly the same thing. The
 calls in the user code could most likely be cleaned up but it
 works just fine. Another drawback compared to your proposal is
 that it allocates another wrapper object per implicit argument
 so it isn't as efficient as it could be. Also it needs to support
 delegates with return values. There seems to be a bug in dmd with
 opApply and template classes (the error message is that wrapper(8)
 expects a function and not WrapDG).
 Anyway, it's fun to play around with:


 // no explicit arg
 class WrapDG(DG,S)
 {
    S data;
    DG dg;
    this(DG dg, S data)
    {
       this.data = data;
       this.dg = dg;
    }
    void opApply()
    {
       dg(data);
    }
 }
 // one explicit arg
 class WrapDG(DG,S,A)
 {
    S data;
    DG dg;
    this(DG dg, S data)
    {
       this.data = data;
       this.dg = dg;
    }
    void opApply(A x)
    {
       dg(data,x);
    }
 }


 class Foo {
    int x;
    void the_callback(int p,int q)
    {
       printf("field x = %d\n",x);
       printf("param p = %d\n",p);
       printf("param q = %d\n\n",q);
    }
 }

 int main()
 {
    Foo t = new Foo();
    t.x = 2;

    // wrap dg_t to get dg2_t
    alias void delegate(int,int) dg_t;
    alias void delegate(int) dg2_t;

    // make wrapper delegate resulting in dg
    alias WrapDG!(dg_t,int,int) wrapper_t;
    wrapper_t wrapper = new wrapper_t(&t.the_callback,4);
    dg2_t dg = &wrapper.opApply; // template opApply needs help

    // try applying it
    printf("calling delegate dg with arg 8\n");
    dg(8);

    // another example: wrap the wrapper to add more implicit args
    WrapDG!(dg2_t,int) wrapper2 = new WrapDG!(dg2_t,int)(dg,16);
    void delegate() dg2 = &wrapper2.opApply;

    // try applying it
    printf("calling delegate dg2 with no args\n");
    dg2();

    return 0;
 }
-- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
Mar 01 2004