digitalmars.D - Control Structures Proposal
- janderson (68/68) Jul 24 2007 I know creating user control structures has been talked about before.
- downs (12/19) Jul 24 2007 You can do better.
- janderson (4/28) Jul 24 2007 I'm aware of this, I don't think its as close as having the delegate in
- Reiner Pope (38/45) Jul 25 2007 I like the idea of adding more allowable operator overloads -- it's a
- Reiner Pope (24/27) Jul 25 2007 I had meant to expand on this point, but I forgot about it. I wanted to
- janderson (29/61) Jul 25 2007 You rise a good point.
- Reiner Pope (2/15) Jul 25 2007 I forgot to attach the implementation I promised. oops.
- nazo (15/119) Jul 25 2007 I think that FunctionStatement is better than opBlock. (the appended
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
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
downs wrote:janderson wrote:I'm aware of this, I don't think its as close as having the delegate in its own block. -JoelFor 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
janderson wrote:downs wrote:I actually agree with you; I also want some way to do custom blocks. All I'm saying is the existing way isn't entirely as bad as you described it. :p --downsjanderson wrote:I'm aware of this, I don't think its as close as having the delegate in its own block. -JoelFor 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 25 2007
downs wrote:janderson wrote:Thats cool. Note that I was commenting on using ({ }) which I think is better then , {} ) but its a matter of preference I guess. -Joeldowns wrote:I actually agree with you; I also want some way to do custom blocks. All I'm saying is the existing way isn't entirely as bad as you described it. :p --downsjanderson wrote:I'm aware of this, I don't think its as close as having the delegate in its own block. -JoelFor 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 25 2007
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
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.I had meant to expand on this point, but I forgot about it. I wanted to 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
Reiner Pope wrote:Reiner Pope wrote: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. -JoelI like the idea of adding more allowable operator overloads -- it's a relatively easy way to allow more user-defined syntaxes.I had meant to expand on this point, but I forgot about it. I wanted to 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
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
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