www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Control Structures Proposal

reply janderson <askme me.com> writes:
I know creating user control structures has been talked about before.

Anyways something to chew on, it occurs to me that D almost has the 
power to create these structures.

For example you can currently do something like this in D (untested).

For({int i=0;}, i!=0, {++i})
({


});


or

If (X)
({

}).
Else
({

));

I was thinking if D provided a block operator like opCall then we would 
be much closer to getting this flexibility.

the syntax would be:

struct [name of struct]
{
	static [returntype] opBlock(void delegate() block, [, parm, param, ...]);
}

ie:

struct For
{
	static void opBlock(void delegate() block, void delegate() init, lazy 
bool condition, void delegate() iteration)
{
	...
}

};

//Then we could write:

For({int i=0;}, i!=0, {++i})
{

}

The next thing that could be done to improve this is to allow all single 
terms to be turned into delegates automatically, like lazy works so we 
don't have to provide the {}.  ie this should then work:

For(int i=0;, i!=0, ++i)
{

}

The last thing that would be necessary to complete the picture is to 
allow contacted statements (like Else in for loop).  This is the most 
difficult syntax to figure out (Any ideas?).  I think the return type 
from the opBlock is one idea, however it suffers from the fact that all 
the processing is done in the second struct its pretty odd.  Perhaps


struct [name of struct]
{
	static [returntype] opBlock(void delegate() block, [, void delegate() 
blockname2, void delegate() blockname3, ...] [, parm, param, ...]);
}

//ie If Else

struct If
{
	static void opBlock(void delegate() block, void delegate() Else);
}

Of course that would mean someone could write my forloop example like 
this as well :(

For (i!=0, ++i)
{

}
init
{
	int i = 0;
}

Ug.


(PS I know lazy may be removed in the future, I hope normal delegates 
will work as its replacement).

-Joel
Jul 24 2007
next sibling parent reply downs <default_357-line yahoo.de> writes:
janderson wrote:
 For example you can currently do something like this in D (untested).
 
 For({int i=0;}, i!=0, {++i})
 ({
 
 
 });

You can do better. void For(lazy void ini, lazy bool cond, lazy void inc, void delegate() dg) { for (ini(); cond(); inc()) dg(); } import std.stdio; void main() { int i=void; For (i=0, i<10, ++i, { writefln(i); }); } Yay for lazy void! --downs
Jul 24 2007
parent reply janderson <askme me.com> writes:
downs wrote:
 janderson wrote:
 For example you can currently do something like this in D (untested).

 For({int i=0;}, i!=0, {++i})
 ({


 });

You can do better. void For(lazy void ini, lazy bool cond, lazy void inc, void delegate() dg) { for (ini(); cond(); inc()) dg(); } import std.stdio; void main() { int i=void; For (i=0, i<10, ++i, { writefln(i); }); } Yay for lazy void! --downs

I'm aware of this, I don't think its as close as having the delegate in its own block. -Joel
Jul 24 2007
parent reply downs <default_357-line yahoo.de> writes:
janderson wrote:
 downs wrote:
 
 janderson wrote:

 For example you can currently do something like this in D (untested).

 For({int i=0;}, i!=0, {++i})
 ({


 });

You can do better. void For(lazy void ini, lazy bool cond, lazy void inc, void delegate() dg) { for (ini(); cond(); inc()) dg(); } import std.stdio; void main() { int i=void; For (i=0, i<10, ++i, { writefln(i); }); } Yay for lazy void! --downs

I'm aware of this, I don't think its as close as having the delegate in its own block. -Joel

way isn't entirely as bad as you described it. :p --downs
Jul 25 2007
parent janderson <askme me.com> writes:
downs wrote:
 janderson wrote:
 downs wrote:

 janderson wrote:

 For example you can currently do something like this in D (untested).

 For({int i=0;}, i!=0, {++i})
 ({


 });

You can do better. void For(lazy void ini, lazy bool cond, lazy void inc, void delegate() dg) { for (ini(); cond(); inc()) dg(); } import std.stdio; void main() { int i=void; For (i=0, i<10, ++i, { writefln(i); }); } Yay for lazy void! --downs

I'm aware of this, I don't think its as close as having the delegate in its own block. -Joel

I'm saying is the existing way isn't entirely as bad as you described it. :p --downs

Thats cool. Note that I was commenting on using ({ }) which I think is better then , {} ) but its a matter of preference I guess. -Joel
Jul 25 2007
prev sibling next sibling parent reply Reiner Pope <some address.com> writes:
I like the idea of adding more allowable operator overloads -- it's a 
relatively easy way to allow more user-defined syntaxes.

However, I think delegates is the wrong level to deal with control 
structures, for two main reasons: there's no ability to do manipulation 
of scope; and you lose information by expressing it all in terms of a 
delegate: the runtime nature of delegates is not required for control 
structures, since their backing code (the block) is inherently known at 
compile time. For these reasons, I think manipulation of D code (via 
char[]s and mixin at present) is better than manipulation of compiled 
code aka delegates/funcptrs. I suspect (and hope) that such manipulation 
of code is what AST macros are going to provide, improving on char[] 
mixins by keeping the manipulation entirely in the AST domain, and 
giving it a nicer syntax.

Your examples which work through delegates suffer the impossibility of 
declaring the increment variable in the For loop:

 For({int i=0;}, i!=0, {++i})
 ({
 
 
 });

this kind of thing won't work, because the declaration of i is scoped to the first delegate, and is invisible to the following two delegates. I believe this is an inherent problem of delegates can't be solved (although it can be worked around, as downs did, by declaring i earlier). Additionally, having access to the *code* rather than just the delegate allows the type system required by the delegate to be made more generic. In fact, it isn't too hard to implement foreach-on-tuples with string mixins to support the following: void main() { alias Tuple!(int, double) T; T t; t[0] = 5; t[1] = 15.0; mixin(Foreach( "x", "t", `x *= 3;` )); writefln("%s %s", t); // prints 15 45 } (implementation attached) A delegate can't express the notion in the above example that the code "x *= 3" is valid both for ints and doubles; however, manipulation of code can.
 (PS I know lazy may be removed in the future, I hope normal delegates 
 will work as its replacement).

Yes, I hope so too. -- Reiner
Jul 25 2007
next sibling parent reply Reiner Pope <some address.com> writes:
Reiner Pope wrote:
 I like the idea of adding more allowable operator overloads -- it's a 
 relatively easy way to allow more user-defined syntaxes.
 

say that operator overloads can provide syntaxes for common things without requiring heavy features like AST macros. I think this is a good thing, and I believe that enough can be done with block structures to warrant including them as an operator overload. However, owing to the scoping issues I mentioned in my previous post, I think your approach is not quite ideal, and I think a preferable approach (although humbler and more limited) would be trailing delegates, which has been discussed a few times in the past. Trailing delegates solve the scope issue by cheating: we say, "it is common to declare some variables which are accessed by the following block, so we will make a special-case syntax for that." By doing this, you get a syntax something like: myList.each() (i) // i is declared here, but type-inferred like foreach // (the first parentheses may be omitted as per normal function calls) { writefln(i); } While the normal reaction is that such special cases are bad, I would argue the contrary, because that is the essence of operator overloading: a combination of special case syntaxes can lead to a variety of useful results. -- Reiner
Jul 25 2007
parent janderson <askme me.com> writes:
Reiner Pope wrote:
 Reiner Pope wrote:
 I like the idea of adding more allowable operator overloads -- it's a 
 relatively easy way to allow more user-defined syntaxes.

say that operator overloads can provide syntaxes for common things without requiring heavy features like AST macros. I think this is a good thing, and I believe that enough can be done with block structures to warrant including them as an operator overload. However, owing to the scoping issues I mentioned in my previous post, I think your approach is not quite ideal, and I think a preferable approach (although humbler and more limited) would be trailing delegates, which has been discussed a few times in the past. Trailing delegates solve the scope issue by cheating: we say, "it is common to declare some variables which are accessed by the following block, so we will make a special-case syntax for that." By doing this, you get a syntax something like: myList.each() (i) // i is declared here, but type-inferred like foreach // (the first parentheses may be omitted as per normal function calls) { writefln(i); } While the normal reaction is that such special cases are bad, I would argue the contrary, because that is the essence of operator overloading: a combination of special case syntaxes can lead to a variety of useful results. -- Reiner

You rise a good point. I was thinking if perhaps D could allow scope-less delegates that only have one sequence of commands (ie int i or int i, int j would be considered one sequence) Then if there was a sequence of sequenced delegates (ie For(void delegate(), void delegate())) everything passed in would be in the same scope of the params. However I realize that may be tricky to do. However another thought would be to simply have to do int i outside the for loop. Not so nice but its a start on the right track. Maybe the best way of doing things though is to use a template as others have suggested in the past. void For(block ini, block cond, block itr)(void delegate() inner) { for (int, cond, itr) inner(); } But that requires a new term as opposed to a new operator and also the delegate to be allowed to be after the function. ie: For (int i=0, i<10, ++i) { } //This would also work: void Test(void delegate() inner) {...} Test{ ... } Not too bad actually. Essentially its simply stripping the ();. You could also do the "Else" sort of thing in the same way I suggested before. I think that is a better solution then having an operator. -Joel
Jul 25 2007
prev sibling parent Reiner Pope <some address.com> writes:
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Reiner Pope wrote:
 void main()
 {
     alias Tuple!(int, double) T;
     T t;
     t[0] = 5;
     t[1] = 15.0;
 
     mixin(Foreach( "x", "t", `x *= 3;` ));
 
     writefln("%s %s", t); // prints 15 45
 }
 (implementation attached)
 

I forgot to attach the implementation I promised. oops.
Jul 25 2007
prev sibling parent nazo <lovesyao gmail.com> writes:
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

I think that FunctionStatement is better than opBlock. (the appended 
patch is for dmdfe.)

Example:
import std.stdio;

void main(){
   int i=void;
   For (i=0, i<10, ++i){
     writefln(i);
   }
   For (i=0, i<10, ++i)writefln(i);
}

void For(lazy void ini, lazy bool cond, lazy void inc, void delegate() dg) {
   for (ini(); cond(); inc()) dg();
}

janderson wrote:
 I know creating user control structures has been talked about before.
 
 Anyways something to chew on, it occurs to me that D almost has the 
 power to create these structures.
 
 For example you can currently do something like this in D (untested).
 
 For({int i=0;}, i!=0, {++i})
 ({
 
 
 });
 
 
 or
 
 If (X)
 ({
 
 }).
 Else
 ({
 
 ));
 
 I was thinking if D provided a block operator like opCall then we would 
 be much closer to getting this flexibility.
 
 the syntax would be:
 
 struct [name of struct]
 {
     static [returntype] opBlock(void delegate() block, [, parm, param, 
 ...]);
 }
 
 ie:
 
 struct For
 {
     static void opBlock(void delegate() block, void delegate() init, 
 lazy bool condition, void delegate() iteration)
 {
     ...
 }
 
 };
 
 //Then we could write:
 
 For({int i=0;}, i!=0, {++i})
 {
 
 }
 
 The next thing that could be done to improve this is to allow all single 
 terms to be turned into delegates automatically, like lazy works so we 
 don't have to provide the {}.  ie this should then work:
 
 For(int i=0;, i!=0, ++i)
 {
 
 }
 
 The last thing that would be necessary to complete the picture is to 
 allow contacted statements (like Else in for loop).  This is the most 
 difficult syntax to figure out (Any ideas?).  I think the return type 
 from the opBlock is one idea, however it suffers from the fact that all 
 the processing is done in the second struct its pretty odd.  Perhaps
 
 
 struct [name of struct]
 {
     static [returntype] opBlock(void delegate() block, [, void 
 delegate() blockname2, void delegate() blockname3, ...] [, parm, param, 
 ...]);
 }
 
 //ie If Else
 
 struct If
 {
     static void opBlock(void delegate() block, void delegate() Else);
 }
 
 Of course that would mean someone could write my forloop example like 
 this as well :(
 
 For (i!=0, ++i)
 {
 
 }
 init
 {
     int i = 0;
 }
 
 Ug.
 
 
 (PS I know lazy may be removed in the future, I hope normal delegates 
 will work as its replacement).
 
 -Joel

Jul 25 2007