Jump to page: 1 2
Thread overview
Dynamic closure vs static closure
Oct 25, 2008
Frank Benoit
Oct 25, 2008
Frank Benoit
Oct 25, 2008
Bill Baxter
Oct 25, 2008
KennyTM~
Oct 25, 2008
Bill Baxter
Oct 25, 2008
KennyTM~
Oct 25, 2008
Yigal Chripun
Oct 25, 2008
Yigal Chripun
Oct 27, 2008
Yigal Chripun
Oct 27, 2008
KennyTM~
Oct 25, 2008
KennyTM~
Oct 25, 2008
Johan Granberg
Oct 26, 2008
Robert Fraser
October 25, 2008
It is great to hear that this issue is getting solved.
How will be the now syntax?

I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site.

void foo( void delegate() dg ){
}
// -or-
void foo2( void delegate() dg ){
}

void bar(){
  int i;
  foo({
    i++;
  });
  // -or-
  foo( scope {
    i++;
  });
}

Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it.

If the signature defines this, the compiler can check that and give more safety.
October 25, 2008
Frank Benoit schrieb:
> It is great to hear that this issue is getting solved.
> How will be the new syntax?
> 
> I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site.
> 
> void foo( void delegate() dg ){
> }
> // -or-

void foo( scope void delegate() dg ){
}

> 
> void bar(){
>   int i;
>   foo({
>     i++;
>   });
>   // -or-
>   foo( scope {
>     i++;
>   });
> }
> 
> Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it.
> 
> If the signature defines this, the compiler can check that and give more safety.
October 25, 2008
On Sat, Oct 25, 2008 at 5:49 PM, Frank Benoit <keinfarbton@googlemail.com> wrote:
> It is great to hear that this issue is getting solved.
> How will be the now syntax?
>
> I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site.
>
> void foo( void delegate() dg ){
> }
> // -or-
> void foo2( void delegate() dg ){
> }
>
> void bar(){
>  int i;
>  foo({
>    i++;
>  });
>  // -or-
>  foo( scope {
>    i++;
>  });
> }
>
> Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it.
>
> If the signature defines this, the compiler can check that and give more safety.

Hmm, this makes it pretty clear I don't know what I'm talking about when it comes to the wheres and hows and whys of dynamic closure creation in D2.  Sorry for the noise.  (please forgive me superdan!)

What I can say for sure, is that D1 has been working pretty well for me, so I just hope the solution doesn't involve a much more cumbersome syntax for things that used to work fine in D1.

--bb
October 25, 2008
Frank Benoit wrote:
> It is great to hear that this issue is getting solved.
> How will be the now syntax?
> 
> I wonder if the distinction between dynamic/static closure shall be done
> on the calling site, or the called site.
> 
> void foo( void delegate() dg ){
> }
> // -or-
> void foo2( scope void delegate() dg ){
> }
> 
> void bar(){
>   int i;
>   foo({
>     i++;
>   });
>   // -or-
>   foo( scope {
>     i++;
>   });
> }
> 
> Because I think, the foo method/function signature has to define if the
> delegate is escaping or not. The caller might not know it.
> 
> If the signature defines this, the compiler can check that and give more
> safety.

It should done on the calling site (the point where delegates are created). Why the called function need to know if the delegate is a closure or not? What they can do is just call the delegate.
October 25, 2008
On Sat, Oct 25, 2008 at 6:30 PM, KennyTM~ <kennytm@gmail.com> wrote:
> Frank Benoit wrote:
>>
>> It is great to hear that this issue is getting solved.
>> How will be the now syntax?
>>
>> I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site.
>>
>> void foo( void delegate() dg ){
>> }
>> // -or-
>> void foo2( scope void delegate() dg ){
>> }
>>
>> void bar(){
>>  int i;
>>  foo({
>>    i++;
>>  });
>>  // -or-
>>  foo( scope {
>>    i++;
>>  });
>> }
>>
>> Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it.
>>
>> If the signature defines this, the compiler can check that and give more safety.
>
> It should done on the calling site (the point where delegates are created). Why the called function need to know if the delegate is a closure or not? What they can do is just call the delegate.

The problem is this

{
    float x = 3.14;
    bar( float delegate (float y){ return x*y ; } );
      // this delegate need to be allocated?
}

If bar is going to hold onto the delegate beyond the end of the scope, then allocation is needed.  The author of bar() may be in a better position to judge that than the caller of bar().  But I'm not sure if that's the only kind of situation to be concerned about.  Are there other cases where only caller knows which it should be?

--bb
October 25, 2008
Bill Baxter wrote:
> On Sat, Oct 25, 2008 at 6:30 PM, KennyTM~ <kennytm@gmail.com> wrote:
>> Frank Benoit wrote:
>>> It is great to hear that this issue is getting solved.
>>> How will be the now syntax?
>>>
>>> I wonder if the distinction between dynamic/static closure shall be done
>>> on the calling site, or the called site.
>>>
>>> void foo( void delegate() dg ){
>>> }
>>> // -or-
>>> void foo2( scope void delegate() dg ){
>>> }
>>>
>>> void bar(){
>>>  int i;
>>>  foo({
>>>    i++;
>>>  });
>>>  // -or-
>>>  foo( scope {
>>>    i++;
>>>  });
>>> }
>>>
>>> Because I think, the foo method/function signature has to define if the
>>> delegate is escaping or not. The caller might not know it.
>>>
>>> If the signature defines this, the compiler can check that and give more
>>> safety.
>> It should done on the calling site (the point where delegates are created).
>> Why the called function need to know if the delegate is a closure or not?
>> What they can do is just call the delegate.
> 
> The problem is this
> 
> {
>     float x = 3.14;
>     bar( float delegate (float y){ return x*y ; } );
>       // this delegate need to be allocated?
> }
> 
> If bar is going to hold onto the delegate beyond the end of the scope,
> then allocation is needed.  The author of bar() may be in a better
> position to judge that than the caller of bar().  But I'm not sure if
> that's the only kind of situation to be concerned about.  Are there
> other cases where only caller knows which it should be?
> 
> --bb

OK. Then both the call site and the function need the "scope", since the function signature alone is surely insufficient to determine if the delegate is closure or not.

Since filling the parameter is just a cloning process, this can be reduced to asking if

   <delegate-type> f = g;

will produce something weird, when one side is a closure, etc.

The simple solution is to make the delegates on stack a super type of delegates on heap.

(Don't mind the "static" keyword yet. I followed the name "static closure" described somewhere else. I agree "static" is too overloaded.)



--------

alias float delegate(in float) FloatDelegate;

// by default delegates are on heap for access safety.
FloatDelegate g, sin_pi;

// but I can declare it only needs to live on stack.
// i.e. the parameters won't jump out of its scope.
static(FloatDelegate) h;

// require allocation on heap. (right? right?)
void store (FloatDelegate f) {
  .g = f;
}

// don't require to allocation on heap
float evaluate_at (static(FloatDelegate) f, in float x) {
  return f(x);
}

// don't just deal with parameters... the return values need to be considered as well.
FloatDelegate square (FloatDelegate f) {
  return float delegate(in float x) { return f(x)*f(x); }
}
FloatDelegate plus_zero_of (static(FloatDelegate) f) {
  auto at_zero = f(0);
  return float delegate(in float x) { return x + at_zero; };
}
static(FloatDelegate) get_zero_function () {
  return static float delegate(in float) { return 0; };
}


{
  float pi = 3.1415926535f;
  // don't require allocation on heap.
  // I know this, therefore the static in front.
  auto cos_pi = static float delegate(in float y){ return cos(pi * y); };
  auto tan_pi = static float delegate(in float y){ return tan(pi * y); };

  // delegate on stack set to another delegate on stack: OK.
  writefln(evaluate_at(tan_pi, 0.2f));

  // require allocation on heap
  sin_pi = float delegate(float y) { return sin(pi * y); }

  // Oops, cos_pi is on stack, but the func sig requires on heap.
  // should generate error.
  //store(cos_pi);
}

// sin_pi already on heap, although the func sig said can be on stack.
// should _not_ generate error.
writefln(evaluate_at(sin_pi, 0.4f));

// g can now stay on stack, although its type is a closure.
g = get_zero_function();

// also ok.
h = get_zero_function();

// this should fail.
h = g;
October 25, 2008
I probably don't understand something here, but why does the delegate itself need to know anything about allocation?

see my comments in the body of your message.

KennyTM~ wrote:
> 
> OK. Then both the call site and the function need the "scope", since the function signature alone is surely insufficient to determine if the delegate is closure or not.
> 
> Since filling the parameter is just a cloning process, this can be reduced to asking if
> 
>    <delegate-type> f = g;
> 
> will produce something weird, when one side is a closure, etc.
> 
> The simple solution is to make the delegates on stack a super type of delegates on heap.
> 
> (Don't mind the "static" keyword yet. I followed the name "static closure" described somewhere else. I agree "static" is too overloaded.)
> 
> 
> 
> --------
> 
> alias float delegate(in float) FloatDelegate;
> 
> // by default delegates are on heap for access safety. FloatDelegate g, sin_pi;
> 
> // but I can declare it only needs to live on stack.
> // i.e. the parameters won't jump out of its scope.
> static(FloatDelegate) h;

change the above to:

scope FloatDelegate h; // note: *same* type for dg

> 
> // require allocation on heap. (right? right?)
> void store (FloatDelegate f) {
>   .g = f;
> }
> 
the above will heap allocate.

> // don't require to allocation on heap
> float evaluate_at (static(FloatDelegate) f, in float x) {
>   return f(x);
> }

float evaluate_at (FloatDelegate f, in float x) {
   return f(x);
}


> 
> // don't just deal with parameters... the return values need to be
> considered as well.
> FloatDelegate square (FloatDelegate f) {
>   return float delegate(in float x) { return f(x)*f(x); }
> }

ok. you'll get heap alloc.

> FloatDelegate plus_zero_of (static(FloatDelegate) f) {
>   auto at_zero = f(0);
>   return float delegate(in float x) { return x + at_zero; };
> }

FloatDelegate plus_zero_of (scope FloatDelegate f) {
   auto at_zero = f(0);
   return float delegate(in float x) { return x + at_zero; };
}

> static(FloatDelegate) get_zero_function () {
>   return static float delegate(in float) { return 0; };
> }

the above seems wrong to me. it's the same as returning scope class
instances..just define:
FloatDelegate get_zero_function () {
   return float delegate(in float) { return 0; };
}

and use:
scope dg1 = get_zero_function(); // this'll prevent heap alloc

> 
> 
> {
>   float pi = 3.1415926535f;
>   // don't require allocation on heap.
>   // I know this, therefore the static in front.
>   auto cos_pi = static float delegate(in float y){ return cos(pi * y); };
>   auto tan_pi = static float delegate(in float y){ return tan(pi * y); };

rewrite as:
 auto cos_pi = scope float delegate(in float y){ return cos(pi * y); };
 auto tan_pi = scope float delegate(in float y){ return tan(pi * y); };

> 
>   // delegate on stack set to another delegate on stack: OK.
>   writefln(evaluate_at(tan_pi, 0.2f));

doesn't matter what "kind" of delegate tan_pi is.
> 
>   // require allocation on heap
>   sin_pi = float delegate(float y) { return sin(pi * y); }
> 
>   // Oops, cos_pi is on stack, but the func sig requires on heap.
>   // should generate error.
>   //store(cos_pi);

either error because you assign a scope object to non-scope or perhaps
better alternatives:
a. you use:
store(cos_pi.dup); // manually alloc on heap
b. not an error. *compiler* automatically allocates a copy on heap.

> }
> 
> // sin_pi already on heap, although the func sig said can be on stack.
> // should _not_ generate error.
> writefln(evaluate_at(sin_pi, 0.4f));
> 
> // g can now stay on stack, although its type is a closure.
> g = get_zero_function();
> 
> // also ok.
> h = get_zero_function();
> 
> // this should fail.
> h = g;

anyway, I think you got my intention by now.
I really don't want to have 3 kinds of function types. I think the
already exsisting separation between function pointers and delegates
could be handled better by adding an implicit cast.


October 25, 2008
On Sat, Oct 25, 2008 at 9:30 AM, Yigal Chripun <yigal100@gmail.com> wrote:
> anyway, I think you got my intention by now.
> I really don't want to have 3 kinds of function types. I think the
> already exsisting separation between function pointers and delegates
> could be handled better by adding an implicit cast.

It would start to become untenable, but I would imagine closures would be implicitly convertible to scope delegates, since in effect they are a subtype, and anywhere a scope delegate could be used, a closure could be used as well.  Add to that implicit conversion from functions to delegates using thunks and bam, you could have a function take one type and it could accept all three.
October 25, 2008
Bill Baxter wrote:

> On Sat, Oct 25, 2008 at 6:30 PM, KennyTM~ <kennytm@gmail.com> wrote:
>> Frank Benoit wrote:
>>>
>>> It is great to hear that this issue is getting solved.
>>> How will be the now syntax?
>>>
>>> I wonder if the distinction between dynamic/static closure shall be done on the calling site, or the called site.
>>>
>>> void foo( void delegate() dg ){
>>> }
>>> // -or-
>>> void foo2( scope void delegate() dg ){
>>> }
>>>
>>> void bar(){
>>>  int i;
>>>  foo({
>>>    i++;
>>>  });
>>>  // -or-
>>>  foo( scope {
>>>    i++;
>>>  });
>>> }
>>>
>>> Because I think, the foo method/function signature has to define if the delegate is escaping or not. The caller might not know it.
>>>
>>> If the signature defines this, the compiler can check that and give more safety.
>>
>> It should done on the calling site (the point where delegates are created). Why the called function need to know if the delegate is a closure or not? What they can do is just call the delegate.
> 
> The problem is this
> 
> {
>     float x = 3.14;
>     bar( float delegate (float y){ return x*y ; } );
>       // this delegate need to be allocated?
> }
> 
> If bar is going to hold onto the delegate beyond the end of the scope, then allocation is needed.  The author of bar() may be in a better position to judge that than the caller of bar().  But I'm not sure if that's the only kind of situation to be concerned about.  Are there other cases where only caller knows which it should be?
> 
> --bb

What about the case of storing a bunch of delegates in a list (thats retaining them) that has a lifetime shorter than the function context?

In this case it would be more efficient to store stack delegates even when some code keeps a reference.

I don't believe that there is a non-problematic way to determine if a delegate has to be on the heap or not. I suggest that this decision is left to the programmer by making heap allocation the default and allowing the calling side to optimize by adding some keyword, for example scope.

For the case of calling library code the programmers will have to rely on documentation.

October 25, 2008
KennyTM~ wrote:
> Bill Baxter wrote:

-- snip --

> 
> OK. Then both the call site and the function need the "scope", since the function signature alone is surely insufficient to determine if the delegate is closure or not.
> 
> Since filling the parameter is just a cloning process, this can be reduced to asking if
> 
>    <delegate-type> f = g;
> 
> will produce something weird, when one side is a closure, etc.
> 
> The simple solution is to make the delegates on stack a super type of delegates on heap.
> 
> (Don't mind the "static" keyword yet. I followed the name "static closure" described somewhere else. I agree "static" is too overloaded.)
> 
> 
> 
> --------
> 

-- snip --

> 
>   // Oops, cos_pi is on stack, but the func sig requires on heap.
>   // should generate error.
>   //store(cos_pi);
> }
> 
> // sin_pi already on heap, although the func sig said can be on stack.
> // should _not_ generate error.
> writefln(evaluate_at(sin_pi, 0.4f));
> 

-- snip --

Oops I caught myself. According to this type system store(cos_pi) should be correct while evaluate_at(sin_pi, 0.4f) will generate an error. It's ridiculous. This can be fixed by switching the role of inheritance of delegates and closures, i.e.

 Delegate dg = Closure // Valid.

 Closure cl = Delegate // Invalid.

Along with (let's get rid of all of them all together...):

 StaticDelegate dg = FuncPtr // Valid.
 Closure cl = FuncPtr // Valid.

 FuncPtr fp = Delegate // Invalid.
 FuncPtr fp = Closure // Invalid.

i.e.

 FuncPtr ---safe-assign---> Closure ---safe-assign---> Delegate.

(Delegate = Static closure, Closure = Dynamic closure)

I'll post my reasoning later, if one finds it unclear...
« First   ‹ Prev
1 2