www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why callers should explicitly document storage classes

reply Mehrdad <wfunction hotmail.com> writes:
Consider this example:

     // In module foo.d:
     void baz(bool condition, lazy int value)
     {
         if (condition)
             writeln(value);
     }

     // In module bar.d:
     import foo;
     bool someCondition() { return false; }
     void main()
     {
         auto vals = [1, 2, 3, 4];
         while (!vals.empty)
             baz(someCondition(), items.moveFront());
     }

There is **absolutely NO WAY** to figure out what's wrong at the calling 
site. You need to check /every/ method call and make sure nothing weird 
is happening (e.g. lazy), and it's pretty much impossible to figure it 
out unless you're intimately familiar with the entire library you're 
calling -- something which (obviously) doesn't scale.

I don't know of a similar example off the top of my head for out/ref, 
but the same idea applies.

Is this convincing enough that we need to document storage classes at 
the CALLING site, rather than just the CALLEE site?
Aug 14 2011
next sibling parent Lutger Blijdestijn <lutger.blijdestijn gmail.com> writes:
Mehrdad wrote:

 Consider this example:
 
      // In module foo.d:
      void baz(bool condition, lazy int value)
      {
          if (condition)
              writeln(value);
      }
 
      // In module bar.d:
      import foo;
      bool someCondition() { return false; }
      void main()
      {
          auto vals = [1, 2, 3, 4];
          while (!vals.empty)
              baz(someCondition(), items.moveFront());
      }
 
 There is **absolutely NO WAY** to figure out what's wrong at the calling
 site. You need to check /every/ method call and make sure nothing weird
 is happening (e.g. lazy), and it's pretty much impossible to figure it
 out unless you're intimately familiar with the entire library you're
 calling -- something which (obviously) doesn't scale.
I'm not convinced that intimately is the right word here. Lazy evaluation is a pretty big deal in the interface of a function, just like ref is. If you have no idea at all what a function does, there is no hope to figure out what the code at the call site does to begin with. Even without lazy/out/ref you could be passing an object with reference semantics to a function which mutates the object, same deal. Not everything is 'const correct', so this may also be surprising if you do not know the function.
 I don't know of a similar example off the top of my head for out/ref,
 but the same idea applies.
 
 Is this convincing enough that we need to document storage classes at
 the CALLING site, rather than just the CALLEE site?
Do you mean literally documenting it or having the compiler require you to state the storage class? If the first, I'd say it depends on the context. The enforce function in phobos for example is clear and well known enough to leave it out. I would not want the latter. But yes, if it's not clear enough then it's a good idea. Preferably it would be clear from the function name and purpose how it operates on the parameters though.
Aug 14 2011
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/14/2011 01:34 PM, Mehrdad wrote:
 Consider this example:

 // In module foo.d:
 void baz(bool condition, lazy int value)
 {
 if (condition)
 writeln(value);
 }

 // In module bar.d:
 import foo;
 bool someCondition() { return false; }
 void main()
 {
 auto vals = [1, 2, 3, 4];
 while (!vals.empty)
 baz(someCondition(), items.moveFront());
 }

 There is **absolutely NO WAY** to figure out what's wrong at the calling
 site. You need to check /every/ method call and make sure nothing weird
 is happening (e.g. lazy), and it's pretty much impossible to figure it
 out unless you're intimately familiar with the entire library you're
 calling -- something which (obviously) doesn't scale.

 I don't know of a similar example off the top of my head for out/ref,
 but the same idea applies.

 Is this convincing enough that we need to document storage classes at
 the CALLING site, rather than just the CALLEE site?
Basically, as I see it what is wrong is that the function is called "baz" (and maybe that lazy arguments are allowed to be side-effecting). out/ref: int x=100; foo(x); assert(x==100);
Aug 14 2011
prev sibling next sibling parent reply "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
On Sun, 14 Aug 2011 14:34:46 +0300, Mehrdad <wfunction hotmail.com> wrote:

 Consider this example:

      // In module foo.d:
      void baz(bool condition, lazy int value)
      {
          if (condition)
              writeln(value);
      }

      // In module bar.d:
      import foo;
      bool someCondition() { return false; }
      void main()
      {
          auto vals = [1, 2, 3, 4];
          while (!vals.empty)
              baz(someCondition(), items.moveFront());
      }

 There is **absolutely NO WAY** to figure out what's wrong at the calling  
 site. You need to check /every/ method call and make sure nothing weird  
 is happening (e.g. lazy), and it's pretty much impossible to figure it  
 out unless you're intimately familiar with the entire library you're  
 calling -- something which (obviously) doesn't scale.

 I don't know of a similar example off the top of my head for out/ref,  
 but the same idea applies.

 Is this convincing enough that we need to document storage classes at  
 the CALLING site, rather than just the CALLEE site?
I've raised this issue during voting for std.parallelism. I think the consensus was that "lazy" before lazy arguments would be appropriate, but "ref" would be mostly pointless, due to complex and reference types. -- Best regards, Vladimir mailto:vladimir thecybershadow.net
Aug 14 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/14/2011 05:36 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 14:34:46 +0300, Mehrdad <wfunction hotmail.com> wrote:

 Consider this example:

 // In module foo.d:
 void baz(bool condition, lazy int value)
 {
 if (condition)
 writeln(value);
 }

 // In module bar.d:
 import foo;
 bool someCondition() { return false; }
 void main()
 {
 auto vals = [1, 2, 3, 4];
 while (!vals.empty)
 baz(someCondition(), items.moveFront());
 }

 There is **absolutely NO WAY** to figure out what's wrong at the
 calling site. You need to check /every/ method call and make sure
 nothing weird is happening (e.g. lazy), and it's pretty much
 impossible to figure it out unless you're intimately familiar with the
 entire library you're calling -- something which (obviously) doesn't
 scale.

 I don't know of a similar example off the top of my head for out/ref,
 but the same idea applies.

 Is this convincing enough that we need to document storage classes at
 the CALLING site, rather than just the CALLEE site?
I've raised this issue during voting for std.parallelism. I think the consensus was that "lazy" before lazy arguments would be appropriate, but "ref" would be mostly pointless, due to complex and reference types.
requiring lazy before lazy arguments basically destroys the reason for lazy being in the language: int foo(lazy 2*3); is not better than int foo({return 2*3});
Aug 14 2011
parent reply "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr <timon.gehr gmx.ch> wrote:

 requiring lazy before lazy arguments basically destroys the reason for  
 lazy being in the language:

 int foo(lazy 2*3);

 is not better than

 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates? -- Best regards, Vladimir mailto:vladimir thecybershadow.net
Aug 14 2011
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/14/2011 10:00 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr <timon.gehr gmx.ch> wrote:

 requiring lazy before lazy arguments basically destroys the reason for
 lazy being in the language:

 int foo(lazy 2*3);

 is not better than

 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
Actually I would rather require lazy arguments to be pure, so that they can be guaranteed to be executed at most once.
Aug 14 2011
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, August 14, 2011 22:25:36 Timon Gehr wrote:
 On 08/14/2011 10:00 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr <timon.gehr gmx.ch> wrote:
 requiring lazy before lazy arguments basically destroys the reason for
 lazy being in the language:
 
 int foo(lazy 2*3);
 
 is not better than
 
 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
Actually I would rather require lazy arguments to be pure, so that they can be guaranteed to be executed at most once.
That still wouldn't be guaranteed, since pure function calls are only optimized out if they're strongly pure and in the same expression. You can't rely on calls to pure functions being optimized. - Jonathan M Davis
Aug 14 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/14/2011 10:36 PM, Jonathan M Davis wrote:
 On Sunday, August 14, 2011 22:25:36 Timon Gehr wrote:
 On 08/14/2011 10:00 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr<timon.gehr gmx.ch>  wrote:
 requiring lazy before lazy arguments basically destroys the reason for
 lazy being in the language:

 int foo(lazy 2*3);

 is not better than

 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
Actually I would rather require lazy arguments to be pure, so that they can be guaranteed to be executed at most once.
That still wouldn't be guaranteed, since pure function calls are only optimized out if they're strongly pure and in the same expression. You can't rely on calls to pure functions being optimized. - Jonathan M Davis
My point was, that if lazy arguments were required to be pure, such a thing _could_ then be guaranteed independently from optimizations that may or may not take place on general pure functions.
Aug 14 2011
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, August 14, 2011 23:20:12 Timon Gehr wrote:
 On 08/14/2011 10:36 PM, Jonathan M Davis wrote:
 On Sunday, August 14, 2011 22:25:36 Timon Gehr wrote:
 On 08/14/2011 10:00 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr<timon.gehr gmx.ch>  
wrote:
 requiring lazy before lazy arguments basically destroys the reason
 for
 lazy being in the language:
 
 int foo(lazy 2*3);
 
 is not better than
 
 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
Actually I would rather require lazy arguments to be pure, so that they can be guaranteed to be executed at most once.
That still wouldn't be guaranteed, since pure function calls are only optimized out if they're strongly pure and in the same expression. You can't rely on calls to pure functions being optimized. - Jonathan M Davis
My point was, that if lazy arguments were required to be pure, such a thing _could_ then be guaranteed independently from optimizations that may or may not take place on general pure functions.
Yes. In theory. It would depend on what Walter was willing to do with the language and the compiler though. At present, pure is purely an optimization and not use to affect semantics at all. However, I'd actually argue that lazy delegates should be effectively treated as pure anyway. Conceptually, what you're doing is delaying the execution of the expression in case it isn't needed. I don't see any reason why the end result should be any different from passing a non-lazy argument. As such, the lazy argument should only be called once regardless. It wouldn't surprise me at all if the implementation doesn't work that way, but I'd argue that it should regardless of purity. From the standpoint of the function, the lazy argument is just like any other argument in terms of its value, and IMHO it should be treated that way. - Jonathan M Davis
Aug 14 2011
prev sibling parent Brad Roberts <braddr puremagic.com> writes:
On Sunday, August 14, 2011 2:30:39 PM, Jonathan M Davis wrote:
 On Sunday, August 14, 2011 23:20:12 Timon Gehr wrote:
 On 08/14/2011 10:36 PM, Jonathan M Davis wrote:
 On Sunday, August 14, 2011 22:25:36 Timon Gehr wrote:
 On 08/14/2011 10:00 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr<timon.gehr gmx.ch>  
wrote:
 requiring lazy before lazy arguments basically destroys the reason
 for
 lazy being in the language:

 int foo(lazy 2*3);

 is not better than

 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
Actually I would rather require lazy arguments to be pure, so that they can be guaranteed to be executed at most once.
That still wouldn't be guaranteed, since pure function calls are only optimized out if they're strongly pure and in the same expression. You can't rely on calls to pure function
s being optimized.
 - Jonathan M Davis
My point was, that if lazy arguments were required to be pure, such a thing _could_ then be guaranteed independently from optimizations that may or may not take place on general pure functions.
Yes. In theory. It would depend on what Walter was willing to do with the language and the compiler though. At present, pure is purely an optimization and not use to affect semantics at all.
The pure keyword doesn't _change_ semantics, but it very much affects semantics, through restriction and the guarantees it provides. The restrictions help enforce some very specific and broadly useful behaviors. But you know this.
 However, I'd actually argue that lazy delegates should be effectively treated 
 as pure anyway. Conceptually, what you're doing is delaying the execution of 
 the expression in case it isn't needed. I don't see any reason why the end 
 result should be any different from passing a non-lazy a
rgument. As such, the
 lazy argument should only be called once regardless. It wouldn't surprise me 
 at all if the implementation doesn't work that way, but I'd argue that it 
 should regardless of purity. From the standpoint of the function, the lazy 
 argument is just like any other argument in terms of its value, and IMHO it 
 should be treated that way.
That's not the way it behaves, or is defined to behave. Lazy parameters are executed 0-N times, entirely based on the implementation of the method itself. It _could_ be re-done to mean 0 or 1, but that's still not always 1. If it was, might as well kill the lazy concept.
 - Jonathan M Davis
Aug 14 2011
prev sibling parent kennytm <kennytm gmail.com> writes:
Timon Gehr <timon.gehr gmx.ch> wrote:
 On 08/14/2011 10:00 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr <timon.gehr gmx.ch> wrote:
 
 requiring lazy before lazy arguments basically destroys the reason for
 lazy being in the language:
 
 int foo(lazy 2*3);
 
 is not better than
 
 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
Actually I would rather require lazy arguments to be pure, so that they can be guaranteed to be executed at most once.
One problem: It is expected that the lazy argument can be re-evaluated. D's `lazy` is actually call-by-name, which allowed stuff like ---- void dotimes(int count, lazy void exp) { for (int i = 0; i < count; i++) exp(); } void foo() { int x = 0; dotimes(10, writef(x++)); } ---- as documented in http://www.d-programming-language.org/lazy-evaluation.html
Aug 14 2011
prev sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Sun, 14 Aug 2011 22:00:34 +0200, Vladimir Panteleev  
<vladimir thecybershadow.net> wrote:

 On Sun, 14 Aug 2011 22:48:18 +0300, Timon Gehr <timon.gehr gmx.ch> wrote:

 requiring lazy before lazy arguments basically destroys the reason for  
 lazy being in the language:

 int foo(lazy 2*3);

 is not better than

 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
I also thought of this idea. Now that pure inference is (being) added to the language, this seems doable and good. -- Simen
Aug 14 2011
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, August 14, 2011 17:39:29 Brad Roberts wrote:
 On Sunday, August 14, 2011 2:30:39 PM, Jonathan M Davis wrote:
 On Sunday, August 14, 2011 23:20:12 Timon Gehr wrote:
 On 08/14/2011 10:36 PM, Jonathan M Davis wrote:
 On Sunday, August 14, 2011 22:25:36 Timon Gehr wrote:
 On 08/14/2011 10:00 PM, Vladimir Panteleev wrote:
 On Sun, 14 Aug 2011 22:48:18 +0300, Timon
 Gehr<timon.gehr gmx.ch>
wrote:
 requiring lazy before lazy arguments basically destroys the
 reason
 for
 lazy being in the language:
 
 int foo(lazy 2*3);
 
 is not better than
 
 int foo({return 2*3});
What about requiring "lazy" only for non-pure delegates?
Actually I would rather require lazy arguments to be pure, so that they can be guaranteed to be executed at most once.
That still wouldn't be guaranteed, since pure function calls are only optimized out if they're strongly pure and in the same expression. You can't rely on calls to pure function
s being optimized.
 - Jonathan M Davis
My point was, that if lazy arguments were required to be pure, such a thing _could_ then be guaranteed independently from optimizations that may or may not take place on general pure functions.
Yes. In theory. It would depend on what Walter was willing to do with the language and the compiler though. At present, pure is purely an optimization and not use to affect semantics at all.
The pure keyword doesn't _change_ semantics, but it very much affects semantics, through restriction and the guarantees it provides. The restrictions help enforce some very specific and broadly useful behaviors. But you know this.
 However, I'd actually argue that lazy delegates should be effectively
 treated as pure anyway. Conceptually, what you're doing is delaying the
 execution of the expression in case it isn't needed. I don't see any
 reason why the end result should be any different from passing a
 non-lazy a
rgument. As such, the
 lazy argument should only be called once regardless. It wouldn't
 surprise me at all if the implementation doesn't work that way, but I'd
 argue that it should regardless of purity. From the standpoint of the
 function, the lazy argument is just like any other argument in terms of
 its value, and IMHO it should be treated that way.
That's not the way it behaves, or is defined to behave. Lazy parameters are executed 0-N times, entirely based on the implementation of the method itself. It _could_ be re-done to mean 0 or 1, but that's still not always 1. If it was, might as well kill the lazy concept.
Oh, I know that it's not always 1, and I'm not suggesting that it always be 1. I'm suggesting that it always be 0 or 1, and that it not actually be called multiple times even if it's referenced multiple times. I don't see why there is any value in having it called multiple times. In concept at least, a non- lazy parameter is the same as a non-lazy except that it isn't evaluated unless it's used. And so I don't see why you'd want to be evaluating it multiple times except that it's probably easier to implement that way. - Jonathan M Davis
Aug 14 2011