Thread overview
[SO] Modifing local class instance in pure function
Jun 17, 2009
Jesse Phillips
Jun 18, 2009
div0
Jun 18, 2009
BCS
Jun 19, 2009
bearophile
Jun 19, 2009
div0
Jun 19, 2009
bearophile
June 17, 2009
http://stackoverflow.com/questions/1008803/how-to-use-pure-in-d-2-0

class TestPure
{
string[] msg;
void addMsg( string s )
{
       msg ~= s;
   }
};

pure TestPure run2()
{
   TestPure t = new TestPure();
   t.addMsg("Test");
   t.addMsg("this.");
   return t;
}

This doesn't work and the answer from CyberShodow is that you could thread inside the pure function modifying the class value at the same time. Isn't that still an issue if TestPure was change to an int?
June 18, 2009
Jesse Phillips wrote:
> http://stackoverflow.com/questions/1008803/how-to-use-pure-in-d-2-0
> 
> class TestPure
> {
> string[] msg;
> void addMsg( string s )
> {
>        msg ~= s;
>    }
> };
> 
> pure TestPure run2()
> {
>    TestPure t = new TestPure();
>    t.addMsg("Test");
>    t.addMsg("this.");
>    return t;
> }
> 
> This doesn't work and the answer from CyberShodow is that you could thread inside the pure function modifying the class value at the same time. Isn't that still an issue if TestPure was change to an int?

Pure functions are not allowed to alter global state. That's what you doing when you create a new object.

http://en.wikipedia.org/wiki/Functional_programming

- --
My enormous talent is exceeded only by my outrageous laziness.
http://www.ssTk.co.uk
June 18, 2009
Reply to div0,

> Pure functions are not allowed to alter global state. That's what you
> doing when you create a new object.
> 
> http://en.wikipedia.org/wiki/Functional_programming
> 

Based on that, cons makes lisp not functional.


June 19, 2009
On Thu, 18 Jun 2009 18:16:36 -0400, div0 <div0@users.sourceforge.net> wrote:

> Pure functions are not allowed to alter global state.
> That's what you doing when you create a new object.
>
> http://en.wikipedia.org/wiki/Functional_programming
>

I once thought as you do.  You are wrong.  Memory allocation is a necessary function, and although it is inherently unpure, it must be allowed in pure functions.

The problem is that the function boundary is where purity is enforced, not inside.  For instance, basic programming 101, split common tasks into functions:


int[] f()
{
   int[] x = new int[5];
   foreach(i, ref n; x) n = i;
   return x;
}

int[] g()
{
   int[] x = new int[6];
   foreach(i, ref n; x) n = i;
   return x;
}

f and g can be pure, because they do not have side effects.  However, consider:

void fill(int[] x)
{
   foreach(i, ref n; x) n = i;
}

int[] f()
{
   int[] x = new int[5];
   fill(x);
   return x;
}

Note that f is still pure, and fill is contextually pure, since in f's context, it is not altering external state.

However, we cannot mark fill as pure, because when called from a non-pure function, it could alter external state.  Therefore, fill cannot be called from f if f is pure.

This is similar to calling a method on an object:

C c = new C;
c.method();

The method call translates to:

C.method(c);

The compiler cannot tell whether c is external state or not, just like it cannot tell that fill() can be contextually pure.  So it is not allowed.

I'm not sure what the solution is.  Something like 'unique' which would ensure that exclusive ownership of an object passes to the method would possibly allow passing mutable state into a pure function.  I'm unsure if there are any plans for something like this.

-Steve
June 19, 2009
Steven Schveighoffer:

>
int[] f()
{
    int[] x = new int[5];
    foreach(i, ref n; x) n = i;
    return x;
}

int[] g()
{
    int[] x = new int[6];
    foreach(i, ref n; x) n = i;
    return x;
}

f and g can be pure, because they do not have side effects.  However, consider:

void fill(int[] x)
{
    foreach(i, ref n; x) n = i;
}

int[] f()
{
    int[] x = new int[5];
    fill(x);
    return x;
}

[...]
I'm not sure what the solution is.  Something like 'unique' which would ensure that exclusive ownership of an object passes to the method would possibly allow passing mutable state into a pure function.  I'm unsure if there are any plans for something like this.
<

Probably there are ways to solve the situation, using unique or something else, but I think that way leads to C++-style madness (see the ridiculously complex lambdas of the last C++, they look like a joke to me. Those designers have missed the point that the essential purpose of a lambda as used in mostly imperative languages is to simplify code and the like, so having one hundred optional features is bad). Keeping pure functions simple is the key to allow their optimizations.

To solve your problem you can change your function fill() to restore its purity (code untested):

pure void range(int n) {
    int[] arr = new int[n];
    foreach (i, ref el; arr)
        el = i;
    return arr;
}

pure int[] f() {
    return range(5);
}

pure int[] g() {
    return range(6);
}

Or you can just accept to not mark fill(), f() and g() as pure.

Bye,
bearophile
June 19, 2009
Steven Schveighoffer wrote:
> On Thu, 18 Jun 2009 18:16:36 -0400, div0 <div0@users.sourceforge.net> wrote:
> 
>> Pure functions are not allowed to alter global state. That's what you doing when you create a new object.
>>
>> http://en.wikipedia.org/wiki/Functional_programming
>>
> 
> I once thought as you do.  You are wrong.  Memory allocation is a necessary function, and although it is inherently unpure, it must be allowed in pure functions.
> 
> The problem is that the function boundary is where purity is enforced, not inside.  For instance, basic programming 101, split common tasks into functions:
> 
> 
> int[] f()
> {
>    int[] x = new int[5];
>    foreach(i, ref n; x) n = i;
>    return x;
> }
> 
> int[] g()
> {
>    int[] x = new int[6];
>    foreach(i, ref n; x) n = i;
>    return x;
> }
> 
> f and g can be pure, because they do not have side effects.  However, consider:
> 
> void fill(int[] x)
> {
>    foreach(i, ref n; x) n = i;
> }
> 
> int[] f()
> {
>    int[] x = new int[5];
>    fill(x);
>    return x;
> }
> 
> Note that f is still pure, and fill is contextually pure, since in f's context, it is not altering external state.
> 
> However, we cannot mark fill as pure, because when called from a non-pure function, it could alter external state.  Therefore, fill cannot be called from f if f is pure.
> 
> This is similar to calling a method on an object:
> 
> C c = new C;
> c.method();
> 
> The method call translates to:
> 
> C.method(c);
> 
> The compiler cannot tell whether c is external state or not, just like
> it cannot tell that fill() can be contextually pure.  So it is not allowed.
> 
> I'm not sure what the solution is.  Something like 'unique' which would ensure that exclusive ownership of an object passes to the method would possibly allow passing mutable state into a pure function.  I'm unsure if there are any plans for something like this.
> 
> -Steve

Nuts I missed the main point of what I meant to say. Though I can see the argument against the heap being global state is necessary or you won't be able to do much with pure functions.

In your example you are returning values types, not objects as in the original question.

Objects are defined to have an identity, so by returning a new object from the function you are not getting the same result for the same inputs.

Does D relax the definition of pure to say a function may return an equivalent object?

- --
My enormous talent is exceeded only by my outrageous laziness.
http://www.ssTk.co.uk
June 19, 2009
div0 Wrote:
> In your example you are returning values types, not objects as in the original question.

Arrays are not value types.  Substitute int[] x with a pointer, same argument applies.

-Steve
June 19, 2009
bearophile Wrote:


> Probably there are ways to solve the situation, using unique or something else, but I think that way leads to C++-style madness (see the ridiculously complex lambdas of the last C++, they look like a joke to me. Those designers have missed the point that the essential purpose of a lambda as used in mostly imperative languages is to simplify code and the like, so having one hundred optional features is bad). Keeping pure functions simple is the key to allow their optimizations.

Such complexity is necessary when you are trying to encapsulate functional benefits with imperative style.  I happen to think we need unique for const/multithreading anyways, why not extend that to help pure functions be modularized?

> 
> To solve your problem you can change your function fill() to restore its purity (code untested):

Yes, I thought of that solution.  I intentionally did not write the code that way to illustrate the point I was trying to make.  I'm expecting the reader to envision larger examples where it's not as convenient to change the modularization point, especially with previously existing functions that are hard to rewrite.  Consider that you can't sort an array you create in a pure function without rewriting sort as a pure function!

> Or you can just accept to not mark fill(), f() and g() as pure.

fill cannot be pure, because it is only contextually pure (pure when called from a pure function).

I'm not saying there is an easy solution, but the problems are not completely solved by the current methodology.

If they can be solved, I think D will be in a class of it's own.

-Steve
June 19, 2009
div0:
> Objects are defined to have an identity, so by returning a new object from the function you are not getting the same result for the same inputs. Does D relax the definition of pure to say a function may return an equivalent object?

Interesting question, I have tried the following code with D2:

import std.stdio: writeln;

class C {}

pure C genc() { return new C; }

void main() {
    writeln(genc() is genc());
}


The generated asm shows that pure of genc is ignored:

With "pure":
main:
L0:     push    EBX
        mov EAX,offset FLAT:_D5temp31C7__ClassZ
        push    EAX
        call    near ptr __d_newclass
        add ESP,4
        mov ECX,offset FLAT:_D5temp31C7__ClassZ
        push    EAX
        sub ESP,4
        push    ECX
        call    near ptr __d_newclass
        add ESP,4
        add ESP,4
        mov EDX,EAX
        mov EBX,1
        pop EAX
        cmp EAX,EDX
        je  L32
        xor EBX,EBX
L32:        mov EAX,offset FLAT:_D3std5stdio6stdoutS3std5stdio4File
        push    EBX
        push    0Ah
        call    near ptr _D3std5stdio4File14__T5writeTbTaZ5writeMFbaZv
        xor EAX,EAX
        pop EBX
        ret


Without "pure":
main:
L0:     push    EBX
        mov EAX,offset FLAT:_D5temp31C7__ClassZ
        push    EAX
        call    near ptr __d_newclass
        add ESP,4
        mov ECX,offset FLAT:_D5temp31C7__ClassZ
        push    EAX
        sub ESP,4
        push    ECX
        call    near ptr __d_newclass
        add ESP,4
        add ESP,4
        mov EDX,EAX
        mov EBX,1
        pop EAX
        cmp EAX,EDX
        je  L32
        xor EBX,EBX
L32:        mov EAX,offset FLAT:_D3std5stdio6stdoutS3std5stdio4File
        push    EBX
        push    0Ah
        call    near ptr _D3std5stdio4File14__T5writeTbTaZ5writeMFbaZv
        xor EAX,EAX
        pop EBX
        ret

I don't know what's happening under the hood into dmd here, Don may give a better answer.

Bye,
bearophile