February 04, 2012
On 02/05/2012 12:15 AM, Era Scarecrow wrote:
>> Probably the restriction was lifted after TDPL was out.
>
>
>> Yes. The compiler will only reorder/run in parallel/optimize if it is
>> safe (not changing execution semantics). Pure can be used to prove
>> that certain optimizations are safe. If a pure function only takes
>> const or immutable arguments, the compiler has more guarantees and can
>> do more things. If a pure function takes mutable arguments, it can be
>> used as a component of other pure functions (important!), but the kind
>> of optimizations that can be performed directly on them are a lot more
>> limited.
>>
>> 'Pure' means that the behavior of the function does not change between
>> invocations with the same/equivalent arguments. This can include
>> mutating actions on the arguments, if those are typed mutable.
>
>
> Even if you changed the signature of the pure function to 'pure int
> squaredPlus(immutable int);' you'd have the same problem; Because the
> int argument it receives is a copy so it won't matter if it was mutable
> or not. (If it were an object, then it would be more enforced).
>
> I'll refer to the language specs to see if I can find an answer on this,
> but it feels wrong allowing access to 'this' on mutable data; I thought
> it could only mutate it's own data in regards to local variables and
> arguments it owned.

the signature I meant looks like

pure int squaredPlus(int)immutable;
February 05, 2012
>
> the signature I meant looks like
>
> pure int squaredPlus(int)immutable;

Which then the only way you could call it, was if the object itself was immutable, which is definitely safe (I think). Hmmm...
February 05, 2012
On 02/05/2012 01:20 AM, Era Scarecrow wrote:
>>
>> the signature I meant looks like
>>
>> pure int squaredPlus(int)immutable;
>
> Which then the only way you could call it, was if the object itself was
> immutable, which is definitely safe (I think). Hmmm...

Alternatively you can use pure int squaredPlus(int)const;, of course.
January 03, 2014
On 2/4/2012 2:04 PM, Era Scarecrow wrote:
> [...]
> struct X {
>     int i;
>     pure int squaredPlus(int x) {
>         return x*x + i
>     }
>     alias squaredPlus sqp;
> }
>
>     X st(15);
>
>     writeln(st.sqp(0));  //15
>     int i1 = st.sqp(10); st.i++;
>     int i2 = st.sqp(10); st.i++;
>     int i3 = st.sqp(10); st.i++;
>     int i4 = st.sqp(10); st.i++;

You can rewrite these like so:

int i1 = sqp(st, 10); st.i++;
int i2 = sqp(st, 10); st.i++;
int i3 = sqp(st, 10); st.i++;
int i4 = sqp(st, 10);

At this point it becomes a little more obvious that these cannot be reordered because their arguments have a shared dependency, just like the following cannot be reordered:

int i = 15;
int i1 = i * i; ++i;
int i2 = i * i; ++i;
int i3 = i * i; ++i;
int i4 = i * i;

I hope we all agree that opMult(int, int) is pure, and that is both safe to reorder its execution with non-dependent args, and unsafe to do so here.

>     assert(i1 == 100); //pass/fail?
> [...]

Fail.  It should be i1 == 115. ;)

Dave

January 03, 2014
On 2/4/2012 12:45 PM, Timon Gehr wrote:
> [...]
> Pure does not imply const in D.
> [...]

I think this is a language defect:

struct Foo
{
    int computed() pure { return x * y; }
    int wrapper() const { return computed() + 5; }

    int x;
    int y;
}

void main()
{
}

src\Bug2.d(4): Error: mutable method Bug2.Foo.computed is not callable using a const object

Surprisingly, this is legal, and "fixes" the issue:

    int computed() const pure { return x * y; }

I say this is a bug because of what a "non-const pure" method would be: a method which could somehow modify 'this', or its members.  I hope we all agree that such a method is not, in fact, pure.  Let's try, just to make sure:

    int computed() pure { return ++x * y; }

Oh noes...this is allowed!!!  Surely this is wrong.  Suppose we rewrote the method like so:

    int computed(ref int x, int y) pure { return ++x * y; }

Would anyone say this is legit?

Dave

January 03, 2014
On Thu, Jan 02, 2014 at 04:31:24PM -0800, David Held wrote: [...]
> I think this is a language defect:
> 
> struct Foo
> {
>     int computed() pure { return x * y; }
>     int wrapper() const { return computed() + 5; }
> 
>     int x;
>     int y;
> }
> 
> void main()
> {
> }
> 
> src\Bug2.d(4): Error: mutable method Bug2.Foo.computed is not
> callable using a const object
> 
> Surprisingly, this is legal, and "fixes" the issue:
> 
>     int computed() const pure { return x * y; }
> 
> I say this is a bug because of what a "non-const pure" method would be: a method which could somehow modify 'this', or its members.  I hope we all agree that such a method is not, in fact, pure.  Let's try, just to make sure:
> 
>     int computed() pure { return ++x * y; }
> 
> Oh noes...this is allowed!!!  Surely this is wrong.  Suppose we rewrote the method like so:
> 
>     int computed(ref int x, int y) pure { return ++x * y; }
> 
> Would anyone say this is legit?
[...]

You're confused because D's purity system is not quite the same as the purity in functional languages. In D, there's the notion of "strong purity" vs. "weak purity":

- Strong purity is what most people would understand as "pure" in the
  functional language sense. In D, this is the purity you get when a
  function is (1) pure, and (2) its arguments have no mutable
  indirections.

- D, however, extends the notion of purity to what is called "weak
  purity", which permits mutation of external data, but with the
  restriction that this mutation can only take place through references
  passed in as arguments to the function.

This is why you need to write "const pure" when you want strong purity, because under the definition of weak purity, the function is allowed to modify data via 'this'.

What's the point of weak purity, you may ask? The basic idea is this: since D is an imperative language, it is most natural to write imperative style code containing mutation of local variables, etc., but from a functional language's POV these are all impure operations and are therefore not allowed in 'pure' code. So you'd have to write 'pure' functions in a convoluted, unnatural style (i.e., in a functional style) in order to conform to the definition of purity. However, this restriction is actually superfluous if all of the side-effects caused by the function is never visible to the outside world. Mutation of local variables is allowed because nobody outside the function will see this mutation. So, as far as the outside world is concerned, the function is still pure, and the fact that it uses 'impure' constructs like mutation of local variables is a mere implementation detail. So modification of local state is permitted in pure functions.

So far so good. But what of weak purity? The motivation behind weak purity comes from the observation that if some helper function, let's call it helper(), can only mutate data via its arguments, then it is legal to call it from a strongly-pure function, because since the strongly-pure function has no outside-observable side-effects, any data it is allowed to mutate can only be local state unobservable to the outside world. This in turn means that it is impossible for the strongly-pure function to pass a mutable reference to any global state to helper(), and therefore, whatever helper() does can only affect state local to the strongly-pure function. As a result, none of helper()'s side-effects (when called from a strongly-pure function) will be visible to the outside world either.

This expanded scope of allowable code in a strongly-pure function is desirable, because it can simplify implementation in many cases. Duplicated code can be avoided (no need to rewrite something just because a pure function needs to call it but it just so happens to not be strongly pure), and you can create class instances inside a strongly-pure function and mutate it by calling its methods -- as long as the methods are subject to the restriction that they only ever mutate state reachable via their arguments, and nothing else. As long as the class instance remains strictly local to the strongly-pure function, the outside world wouldn't know any better (nor care) -- the function is still strongly pure as far as it is concerned.  Thus, we can expand the scope of what can be considered pure in D, while still enjoying the benefits of purity (no (observable) side-effects, cacheability, etc.).

So in view of this, D's 'pure' keyword has come to mean 'weakly pure', that is, modification of data is allowed via indirections passed in as function arguments (the 'this' pointer is considered an argument), but direct modification of global state is strictly prohibited. Finally, a (weakly) pure function is strongly-pure when its arguments contain no mutable indirections, because then, together with the weakly-pure restriction, this implies that the function cannot modify any external state at all, since the only permitted avenue of doing so -- via function arguments -- contains no mutable indirections, so the function is unable to reach any outside state.

This is the reason for Timon's response that you need to write 'const pure' when you mean "strongly-pure"; "pure" by itself means "weakly-pure" because the 'this' pointer is mutable by default.


T

-- 
Маленькие детки - маленькие бедки.
1 2
Next ›   Last »