www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - pure member functions

reply Jonathan M Davis <jmdavisProg gmx.com> writes:
I assume that if you declare a member function as pure, then all of its 
parameters - including the invisible this - are included in that. That is, if 
all of them - including the invisible this - have the same value, then the 
result will be the same.

I'm not quite sure way (perhaps because pure is very much a thing of functional 
programming rather than object-oriented programming), but I feel very weird 
marking a member function as pure. Is there really in point to it? Does it act 
the same as if it were a pure static function that had an explicit parameter
for 
the object rather than the invisible this? If that's the case, then it would 
seem like it would be sensible to mark virtually every member function as pure. 
The few that would be unable to would likely be due to accessing globals, which 
is usually a no-no anyway.

Am I misunderstanding something here? Is there a reason _not_ to use pure on 
every member function that you can?

- Jonathan M Davis
Sep 18 2010
parent reply bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 I assume that if you declare a member function as pure, then all of its 
 parameters - including the invisible this - are included in that. That is, if 
 all of them - including the invisible this - have the same value, then the 
 result will be the same.
This D2 program runs with no errors, and here there isn't a D language/compiler bug: struct Foo { int x; this (int xx) { this.x = xx; } pure int bar() { return x; } } void main() { Foo f = Foo(1); assert(f.bar() == 1); f.x *= 2; assert(f.bar() == 2); } Bye, bearophile
Sep 19 2010
parent reply Don <nospam nospam.com> writes:
bearophile wrote:
 Jonathan M Davis:
 
 I assume that if you declare a member function as pure, then all of its 
 parameters - including the invisible this - are included in that. That is, if 
 all of them - including the invisible this - have the same value, then the 
 result will be the same.
This D2 program runs with no errors, and here there isn't a D language/compiler bug: struct Foo { int x; this (int xx) { this.x = xx; } pure int bar() { return x; } } void main() { Foo f = Foo(1); assert(f.bar() == 1); f.x *= 2; assert(f.bar() == 2); } Bye, bearophile
You do need to be careful about concluding how 'pure' works based on the current behaviour of the compiler. There's a trap here. What if you use a hypothetical startTimer() function which executes a delegate every few clock ticks? void main() { Foo f = Foo(1); startTimer( () { f.x++; }); scope(exit) killTimer(); assert(f.bar() == 1); // may fail! f.x *= 2; assert(f.bar() == 2); } I actually think that 'pure' on a member function can only mean, it's cacheably pure if and only if 'this' can be cast to immutable. Which includes the important case where the call is made from a pure function (this implies that the 'this' pointer is either a local variable of a pure function, or an immutable object). Since pure functions cannot call impure functions, they can't do any of this nasty asynchronous stuff. In fact I think in general, that's how pure should work: once you're inside 'pure', you should be able to pass even mutable objects into pure functions. This is possible because although it's mutable, the entire code that modifies it is confined to a single function. So the compiler can determine if it is cacheably pure without performing any kind of whole program analysis. But this is just my opinion. I don't know if it will eventually work that way.
Sep 20 2010
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 20 Sep 2010 15:45:10 -0400, Don <nospam nospam.com> wrote:

 bearophile wrote:
 Jonathan M Davis:

 I assume that if you declare a member function as pure, then all of  
 its parameters - including the invisible this - are included in that.  
 That is, if all of them - including the invisible this - have the same  
 value, then the result will be the same.
This D2 program runs with no errors, and here there isn't a D language/compiler bug: struct Foo { int x; this (int xx) { this.x = xx; } pure int bar() { return x; } } void main() { Foo f = Foo(1); assert(f.bar() == 1); f.x *= 2; assert(f.bar() == 2); } Bye, bearophile
You do need to be careful about concluding how 'pure' works based on the current behaviour of the compiler. There's a trap here. What if you use a hypothetical startTimer() function which executes a delegate every few clock ticks? void main() { Foo f = Foo(1); startTimer( () { f.x++; }); scope(exit) killTimer(); assert(f.bar() == 1); // may fail! f.x *= 2; assert(f.bar() == 2); }
Wouldn't f have to be shared for this to be asynchronous?
 I actually think that 'pure' on a member function can only mean, it's  
 cacheably pure if and only if 'this' can be cast to immutable. Which  
 includes the important case where the call is made from a pure function  
 (this implies that the 'this' pointer is either a local variable of a  
 pure function, or an immutable object).
 Since pure functions cannot call impure functions, they can't do any of  
 this nasty asynchronous stuff.
I think it's ok for a function to be pure if all the arguments are unshared, regardless of immutability. However, in order to cache the return value, the reference itself must not be used as the key, but the entire data of the reference. Even if it's immutable, wouldn't you not want to cache the return values between two identical immutable objects? -Steve
Sep 20 2010
parent reply Don <nospam nospam.com> writes:
Steven Schveighoffer wrote:
 On Mon, 20 Sep 2010 15:45:10 -0400, Don <nospam nospam.com> wrote:
 
 bearophile wrote:
 Jonathan M Davis:

 I assume that if you declare a member function as pure, then all of 
 its parameters - including the invisible this - are included in 
 that. That is, if all of them - including the invisible this - have 
 the same value, then the result will be the same.
This D2 program runs with no errors, and here there isn't a D language/compiler bug: struct Foo { int x; this (int xx) { this.x = xx; } pure int bar() { return x; } } void main() { Foo f = Foo(1); assert(f.bar() == 1); f.x *= 2; assert(f.bar() == 2); } Bye, bearophile
You do need to be careful about concluding how 'pure' works based on the current behaviour of the compiler. There's a trap here. What if you use a hypothetical startTimer() function which executes a delegate every few clock ticks? void main() { Foo f = Foo(1); startTimer( () { f.x++; }); scope(exit) killTimer(); assert(f.bar() == 1); // may fail! f.x *= 2; assert(f.bar() == 2); }
Wouldn't f have to be shared for this to be asynchronous?
That's an excellent point. 'pure' was put into the language long before 'shared' and '__gshared'. It could now just mean, "doesn't use static, globals, shared, or __gshared". And then cachable pure is just: pure, + all reference parameters are immutable. If this becomes the rule, it seems likely that pure functions would become far more common than impure ones.
 I actually think that 'pure' on a member function can only mean, it's 
 cacheably pure if and only if 'this' can be cast to immutable. Which 
 includes the important case where the call is made from a pure 
 function (this implies that the 'this' pointer is either a local 
 variable of a pure function, or an immutable object).
 Since pure functions cannot call impure functions, they can't do any 
 of this nasty asynchronous stuff.
I think it's ok for a function to be pure if all the arguments are unshared, regardless of immutability. However, in order to cache the return value, the reference itself must not be used as the key, but the entire data of the reference. Even if it's immutable, wouldn't you not want to cache the return values between two identical immutable objects?
Possibly, but my guess is that it would take too long to check.
Sep 20 2010
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 20 Sep 2010 16:26:44 -0400, Don <nospam nospam.com> wrote:

 Steven Schveighoffer wrote:
  I think it's ok for a function to be pure if all the arguments are  
 unshared, regardless of immutability.  However, in order to cache the  
 return value, the reference itself must not be used as the key, but the  
 entire data of the reference.  Even if it's immutable, wouldn't you not  
 want to cache the return values between two identical immutable objects?
Possibly, but my guess is that it would take too long to check.
This is why I hate the idea of automatic caching -- how does the compiler know that it would be too long? What if the operation takes 15 seconds, and to do the memcmp takes 15 milliseconds? -Steve
Sep 21 2010