September 22, 2010
On Wed, 22 Sep 2010 04:13:34 -0400, Don <nospam@nospam.com> wrote:

> Don wrote:
>> The docs currently state that:
>
>> PROPOSAL:
>> Drop the first requirement. Only one requirement is necessary:
>>  A pure function does not read or write any global mutable state.
>>
>
> Wow. It seems that not one person who has responded so far has understood this proposal! I'll try again. Under this proposal:
>
> If you see a function which has mutable parameters, but is marked as 'pure', you can only conclude that it doesn't use global variables. That's not much use on it's own. Let's call this a 'weakly-pure' function.
>
> However, if you see a function maked as 'pure', which also has only immutable parameters, you have the same guarantee which 'pure' gives us as the moment. Let's call this a 'strongly-pure' function.
>
> The benefit of the relaxed rule is that a strongly-pure function can call a weakly-pure functions, while remaining strongly-pure.
> This allows very many more functions to become strongly pure.
>
> The point of the proposal is *not* to provide the weak guarantee. It is to provide the strong guarantee in more situations.

I just thought of something -- we may be defining the common case.  For example, D breaks from the default of shared globals in C because most of the time, variables *aren't* shared.  When I first heard of shared, I thought surely Walter was losing his mind.  Why would shared not be the default, clearly as it's done in C!  But after having used the language, I see that it would have been a huge issue if I had to mark everything as unshared.

If we can define weakly pure functions this way, they most likely will be way more common than unpure functions.  I know I avoid accessing global variables in most of my functions.  Think about a range, almost all the methods in a range can be weakly pure.  So that means you need to mark every function as pure.

Would it not be less tedious to mark unpure functions instead of pure functions?  Or am I just going too far with this?

OR, maybe we could say, mark strongly pure functions as pure, mark functions that access global data as something else (global?) and weakly pure functions just aren't marked.

-Steve
September 22, 2010
sclytrack wrote:
>> weaklypure void reverse(int[] x)
>> {
>>     for(int i = 0; i * 2 < x.length; i++)
>>         swap(x[i], x[$-1-i]);
>> }
>> pure int foo(const(int)[] x)
>> {
>>      auto x2 = x.dup;
>>      reverse(x2);
>>      // do some calculation on x2
>>      ...
>>      return calculation;
>> }
> 
> noglobal void reverse(MyClass x)
> {
>   x.text = "";
> }
> 
> So weakly-pure should not access global stuff. But that is harder
> to track by the compiler with mutable parameters.

Not really. You just need to disallow shared, __gshared in pure function declarations.

> Or weakly-pure is only noglobal when called in pure routines, because of the
> immutable barrier.
> Unwritten contract, not checked by the compiler?  (Like property getters shouldn't
> modify the "state" of the object.)

No. The contract is, that the function only has access to the parameters you have given it. It depends _only_ on its parameters.
 If you've made all parameters immutable or value types, it's strongly pure.
These additional contracts come automatically from the type system.
September 22, 2010
On Wednesday, September 22, 2010 14:19:42 Steven Schveighoffer wrote:
> I just thought of something -- we may be defining the common case.  For example, D breaks from the default of shared globals in C because most of the time, variables *aren't* shared.  When I first heard of shared, I thought surely Walter was losing his mind.  Why would shared not be the default, clearly as it's done in C!  But after having used the language, I see that it would have been a huge issue if I had to mark everything as unshared.
> 
> If we can define weakly pure functions this way, they most likely will be way more common than unpure functions.  I know I avoid accessing global variables in most of my functions.  Think about a range, almost all the methods in a range can be weakly pure.  So that means you need to mark every function as pure.
> 
> Would it not be less tedious to mark unpure functions instead of pure functions?  Or am I just going too far with this?
> 
> OR, maybe we could say, mark strongly pure functions as pure, mark functions that access global data as something else (global?) and weakly pure functions just aren't marked.

That seems like it would clearly mark out the three types of functions, though I'm not quite sure what all the implications are of having to mark in a function's signature that it accesses global data. That might be untenable. However, it would be a *lot* clearer than having to worry about what kind of pure a particular function is. It would also discourage accessing global state, which would generally be good - though shouldn't immutable global state still be accessible even from a pure function? Regardless, requiring "global" or some other keyword  would likely be too much of a breaking change at this point - though it might be worth it.

In any case, while I'm not sure I entirely understand the discussion in this thread, I like where it's going. As it stands, pure is too restrictive to be generally useful. It's still worth having, but it doesn't do much good generally. Being able to call weakly-pure functions from pure ones would improve things considerably, even if weakly-pure functions got no actual benefit from being weakly-pure.

- Jonathan M Davis
September 22, 2010
Steven Schveighoffer wrote:
>> A pure function also cannot modify any data via its parameters. In other words, its parameters must be transitively const.
> 
> Yes, a strongly pure function must have those traits.

No, that would be a weakly pure one.

> But, purity exists to allow for optimization.  A weakly pure function cannot be optimized anymore than a normal function, but a strongly pure can still be optimized even if it calls weakly-pure functions.
> 
> I'll give you an example (with a new keyword to help you understand the difference):
> 
> weaklypure void reverse(int[] x)
> {
>    for(int i = 0; i * 2 < x.length; i++)
>        swap(x[i], x[$-1-i]);
> }
> 
> pure int foo(const(int)[] x)
> {
>     auto x2 = x.dup;
>     reverse(x2);
>     // do some calculation on x2
>     ...
>     return calculation;
> }
> 
> Hopefully you can see how foo still is pure, while being able to call reverse.  Essentially, weakly-pure functions can be used to build strongly-pure functions.

This isn't what I was talking about - you didn't modify the contents of the array x[].
September 22, 2010
> > So weakly-pure should not access global stuff. But that is harder to track by the compiler with mutable parameters.
> Not really. You just need to disallow shared, __gshared in pure function declarations.
> > Or weakly-pure is only noglobal when called in pure routines, because of the
> > immutable barrier.
> > Unwritten contract, not checked by the compiler?  (Like property getters shouldn't
> > modify the "state" of the object.)
> No. The contract is, that the function only has access to the parameters
> you have given it. It depends _only_ on its parameters.
>   If you've made all parameters immutable or value types, it's strongly
> pure.
> These additional contracts come automatically from the type system.


pure doStuff( [immutable] int a, [unshared] MyClass a)
{
}

1) If shared it must be immutable.
2) If mutable it must be not shared.



September 22, 2010
Walter Bright Wrote:
> A pure function also cannot modify any data via its parameters. In other words, its parameters must be transitively const.

A weakly pure function should be able to take mutable inputs and modify them. When called from a strongly pure function, the mutable data can only be local variables.
September 22, 2010
Walter Bright Wrote:

> Steven Schveighoffer wrote:
> >> A pure function also cannot modify any data via its parameters. In other words, its parameters must be transitively const.
> > 
> > Yes, a strongly pure function must have those traits.
> 
> No, that would be a weakly pure one.
> 
> > But, purity exists to allow for optimization.  A weakly pure function cannot be optimized anymore than a normal function, but a strongly pure can still be optimized even if it calls weakly-pure functions.
> > 
> > I'll give you an example (with a new keyword to help you understand the difference):
> > 
> > weaklypure void reverse(int[] x)
> > {
> >    for(int i = 0; i * 2 < x.length; i++)
> >        swap(x[i], x[$-1-i]);
> > }
> > 
> > pure int foo(const(int)[] x)
> > {
> >     auto x2 = x.dup;
> >     reverse(x2);
> >     // do some calculation on x2
> >     ...
> >     return calculation;
> > }
> > 
> > Hopefully you can see how foo still is pure, while being able to call reverse.  Essentially, weakly-pure functions can be used to build strongly-pure functions.
> 
> This isn't what I was talking about - you didn't modify the contents of the array x[].

The problem is that you can't call reverse or sort unless the function is 'pure,' but the function can not be pure because it modifies its own parameters which means his example function cannot be marked pure even though it is.
September 22, 2010
Steven Schveighoffer Wrote:

> I just thought of something -- we may be defining the common case.  For example, D breaks from the default of shared globals in C because most of the time, variables *aren't* shared.  When I first heard of shared, I thought surely Walter was losing his mind.  Why would shared not be the default, clearly as it's done in C!  But after having used the language, I see that it would have been a huge issue if I had to mark everything as unshared.
> 
> If we can define weakly pure functions this way, they most likely will be way more common than unpure functions.  I know I avoid accessing global variables in most of my functions.  Think about a range, almost all the methods in a range can be weakly pure.  So that means you need to mark every function as pure.
> 
> Would it not be less tedious to mark unpure functions instead of pure functions?  Or am I just going too far with this?
> 
> OR, maybe we could say, mark strongly pure functions as pure, mark functions that access global data as something else (global?) and weakly pure functions just aren't marked.
> 
> -Steve

This is interesting, I'm not sure if either of these approaches could make it into D2, though a conservative approach was originally taken only because it was the most easy to prove.

I also find it interesting that D actually takes the 3-stage approach to several things. mutable, const, immutable; @safe, @trusted, @system. And this being unpure, contained, pure.

D recognizes the importance of these stages in other areas, so I feel it would be a miss if there is not a good reason the proposal doesn't work. Sadly what you learn from one of the stated areas (mutability, safety, purity) can't be applied to the other. But the pattern is there.
September 23, 2010
On Wed, 22 Sep 2010 13:10:36 -0400, Steven Schveighoffer <schveiguy@yahoo.com> wrote:

> On Wed, 22 Sep 2010 12:00:16 -0400, Robert Jacques <sandford@jhu.edu> wrote:
>
>> On Wed, 22 Sep 2010 11:53:22 -0400, Don <nospam@nospam.com> wrote:
>>
>>> Robert Jacques wrote:
>>>> On Wed, 22 Sep 2010 04:13:34 -0400, Don <nospam@nospam.com> wrote:
>>>>
>>>>> Don wrote:
>>>>>> The docs currently state that:
>>>>>
>>>>>> PROPOSAL:
>>>>>> Drop the first requirement. Only one requirement is necessary:
>>>>>>  A pure function does not read or write any global mutable state.
>>>>>>
>>>>>
>>>>> Wow. It seems that not one person who has responded so far has understood this proposal! I'll try again. Under this proposal:
>>>>  Funny, your re-iteration appears to coincided to my previous understanding. So either I've mis-understood twice, or I didn't sufficiently demonstrate my understanding when I made my critique. :) That said, I do think this version is much clearer and understandable.
>>>>
>>>>> If you see a function which has mutable parameters, but is marked as 'pure', you can only conclude that it doesn't use global variables. That's not much use on it's own. Let's call this a 'weakly-pure' function.
>>>>>
>>>>> However, if you see a function maked as 'pure', which also has only immutable parameters, you have the same guarantee which 'pure' gives us as the moment. Let's call this a 'strongly-pure' function.
>>>>>
>>>>> The benefit of the relaxed rule is that a strongly-pure function can call a weakly-pure functions, while remaining strongly-pure.
>>>>> This allows very many more functions to become strongly pure.
>>>>>
>>>>> The point of the proposal is *not* to provide the weak guarantee. It is to provide the strong guarantee in more situations.
>>>>  The problem from my point of view is that the programmer can not declare that a function should be 'strongly-pure' or 'weakly-pure'.
>>>
>>> Yes they can. They just need to declare the parameters to be immutable.
>>
>> What about value types?
>
> Value types are implicitly convertable to immutable, so they can be strongly-pure.

No their not. Remember, arrays and other structs are value types in the type system. Logically, they may be reference types, but as far as their type signature goes, they are value types.

> The problem I think Robert is referring to is, a person cannot always tell just from looking at a signature that a type is strongly-pure qualified or not.

And that you can not manually specify one or the other.

> For example, is this function weak or strong?
>
> pure int foo(T t);
>
> But the compiler will be able to tell.  I think adding a __traits(isStronglyPure, symbol) will be good for those rare occasions where you really want to ensure purity.
>
> static assert(__traits(isStronglyPure, foo));
>
> -Steve

This would work, but it wouldn't be self-documenting, etc.
September 23, 2010
On Wed, 22 Sep 2010 15:52:03 -0400, Don <nospam@nospam.com> wrote:

> Steven Schveighoffer wrote:
>> On Wed, 22 Sep 2010 15:21:57 -0400, klickverbot <see@klickverbot.at> wrote:
>>
>>> On 9/22/10 9:14 PM, Steven Schveighoffer wrote:
>>>> Hypothetical counter-case
>>>>
>>>> struct S
>>>> {
>>>> version(stronglypure)
>>>> string s;
>>>> else
>>>> char[] s;
>>>> }
>>>>
>>>> pure foo(S s); // changes strength depending on S' contents
>>>>
>>>> -Steve
>>>
>>> This is a change to the signature of foo – S with the stronglypure version defined and S without it are two completely distinct types.
>>  Wait, I didn't change foo's signature at all.  This is what the OP meant by long-range changes.  S can be defined far away from foo, and out of the author of foo's control.
>>  -Steve
>
> Is that really what he meant?

Yes.

> Note that foo() would need to be recompiled, and the silent change in strength would occur only if it still compiles despite a significant change in the definition of one of types.
>
> If you change the definition of a type, you're always going to have influences everywhere it's used in a function definition. I'd hardly call that a 'long-range' change.

Consider the case of adding some new feature Y to S or a change in the internal implementation. The API from foo's perspective doesn't change one iota, but the purity could.