www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Immutable nested functions

reply Tomek =?ISO-8859-2?Q?Sowi=F1ski?= <just ask.me> writes:
A while ago I pointed out that the result of an immutably pure function (all
arguments immutable, doesn't mutate globals) can be safely converted to
immutable. More here:
http://d.puremagic.com/issues/show_bug.cgi?id=5081

It helps with building complex immutable structures. Problem is, virtually
every construction site is different so one is forced to define a new
initializer function every time. To illustrate:

void main() {
    immutable Node* init_leaf = ... ;
    uint breadth = ... ;
    immutable Node* tree = grow_tree(init_leaf, breadth);
}

Node* grow_tree(immutable Node* init_leaf, uint breadth) pure {
    Node* root = new Node;
    foreach (0..breadth) {
        Node* leaf = new Node(init_leaf);
        leaf.parent = root;
        root.leaves ~= leaf;
    }
    return root;
}

I tried to find a way to create ad-hoc functions conveniently. Naturally, I
turned to nested functions:

void main() {
    immutable Node* init_leaf = ... ;
    uint breadth = ... ;
    
    Node* grow_tree() pure immutable {
        Node* root = new Node;
        foreach (0..breadth) {
            Node* leaf = new Node(init_leaf);
            leaf.parent = root;
            root.leaves ~= leaf;
        }
        return root;
    }
    
    immutable Node* tree = grow_tree();
}

Nested functions to be immutably pure must also guarantee that nothing gets
mutated through its stack frame pointer. But there's a problem -- the compiler
won't accept 'immutable' on a nested function. I think it should -- just like
an immutable member function (e.g. in a class) is allowed to play only with
immutable members of that class, an immutable nested function should be allowed
to play only with the immutable members of the stack frame. It may seem a
lesser change but I'm pretty excited as it solves the long-standing problems
with immutable structure initialization.

Excitement aside, I got questions:

1. I'm proposing to proliferate the concept of immutability from 'this'
reference to a stack frame pointer. Although I'm confident it makes sense as
these are interchangeable in delegates, I could use some criticism to either
reject or strengthen the idea.

2. What about delegates? Should there be means to express a "delegate that
doesn't mutate through its 'this'/stack frame pointer"? What should the syntax
be for defining such delegate type and for lambdas (delegate literals)?

3. (vaguely related) Should there be means to express annotated delegates in
general (e.g. pure, nothrow). What about annotated lambdas?

-- 
Tomek
Jan 04 2011
next sibling parent Tomek =?ISO-8859-2?Q?Sowi=F1ski?= <just ask.me> writes:
Tomek Sowi=F1ski napisa=B3:

 [snip]
Two days, no answer. I was told that silence means agreement on this NG but= this is extreme ;-) Seriously, what did I do wrong? Too long/boring post? --=20 Tomek
Jan 06 2011
prev sibling next sibling parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Tomek Sowinski" <just ask.me> wrote in message 
news:20110104222343.00004d47 unknown...
 Nested functions to be immutably pure must also guarantee that nothing 
 gets mutated through its stack frame pointer. But there's a problem -- the 
 compiler won't accept 'immutable' on a nested function. I think it 
 should -- just like an immutable member function (e.g. in a class) is 
 allowed to play only with immutable members of that class, an immutable 
 nested function should be allowed to play only with the immutable members 
 of the stack frame. It may seem a lesser change but I'm pretty excited as 
 it solves the long-standing problems with immutable structure 
 initialization.
just like an immutable member function (e.g. in a class) is allowed to play 
only with immutable members of that class
Defining a member function to be immutable doesn't mean it can only access immutable member variables, it means it must be called with an immutable class reference. class C { void fun() immutable {} } void main() { auto c = new C(); c.fun(); // error auto i = new immutable C(); i.fun(); // ok } For nested functions, the reference is to the enclosing function's stack frame. What does it mean to have this be immutable? Maybe this makes sense if EVERY variable in the enclosing stack frame is immutable? Is there any reason you couldn't just use static nested pure functions? void main() { static pure Node* grow_tree(int breadth) // strongly pure { .... } immutable Node* tree = grow_tree(...); }
Jan 06 2011
parent Tomek =?ISO-8859-2?Q?Sowi=F1ski?= <just ask.me> writes:
Daniel Murphy napisa=B3:

 Defining a member function to be immutable doesn't mean it can only acces=
s=20
 immutable member variables, it means it must be called with an immutable=
=20
 class reference.
=20
 class C
 {
   void fun() immutable {}
 }
=20
 void main()
 {
   auto c =3D new C();
   c.fun(); // error
   auto i =3D new immutable C();
   i.fun(); // ok
 }
=20
 For nested functions, the reference is to the enclosing function's stack=
=20
 frame.  What does it mean to have this be immutable?  Maybe this makes se=
nse=20
 if EVERY variable in the enclosing stack frame is immutable?
Good point. I think the concept of immutable nested functions is still a so= lid improvement, even with requiring every variable of the enclosing stack = frame to be immutable.
 Is there any reason you couldn't just use static nested pure functions?
=20
 void main()
 {
   static pure Node* grow_tree(int breadth) // strongly pure
   {
     ....
   }
   immutable Node* tree =3D grow_tree(...);
 }
I fear one would need to tediously add arguments for *every* ingredient in = the scope to be able to cook them inside. And if an ingredient turns out no= t necessary as the software evolves, there's the tedium of removing it from= the nested function signature, which -- let's face it -- nobody does. Init= ializing immutable structures is fairly common and lack of a convenient way= to do it has been recognized as one of the shortcomings of the const syste= m. It can and should be solved. --=20 Tomek
Jan 07 2011
prev sibling parent reply Don <nospam nospam.com> writes:
Tomek Sowin'ski wrote:
 A while ago I pointed out that the result of an immutably pure function (all
arguments immutable, doesn't mutate globals) can be safely converted to
immutable. More here:
 http://d.puremagic.com/issues/show_bug.cgi?id=5081
 
 It helps with building complex immutable structures. Problem is, virtually
every construction site is different so one is forced to define a new
initializer function every time. To illustrate:
 
 void main() {
     immutable Node* init_leaf = ... ;
     uint breadth = ... ;
     immutable Node* tree = grow_tree(init_leaf, breadth);
 }
 
 Node* grow_tree(immutable Node* init_leaf, uint breadth) pure {
     Node* root = new Node;
     foreach (0..breadth) {
         Node* leaf = new Node(init_leaf);
         leaf.parent = root;
         root.leaves ~= leaf;
     }
     return root;
 }
 
 I tried to find a way to create ad-hoc functions conveniently. Naturally, I
turned to nested functions:
 
 void main() {
     immutable Node* init_leaf = ... ;
     uint breadth = ... ;
     
     Node* grow_tree() pure immutable {
         Node* root = new Node;
         foreach (0..breadth) {
             Node* leaf = new Node(init_leaf);
             leaf.parent = root;
             root.leaves ~= leaf;
         }
         return root;
     }
     
     immutable Node* tree = grow_tree();
 }
 
 Nested functions to be immutably pure must also guarantee that nothing gets
mutated through its stack frame pointer. But there's a problem -- the compiler
won't accept 'immutable' on a nested function. I think it should -- just like
an immutable member function (e.g. in a class) is allowed to play only with
immutable members of that class, an immutable nested function should be allowed
to play only with the immutable members of the stack frame. It may seem a
lesser change but I'm pretty excited as it solves the long-standing problems
with immutable structure initialization.
 
 Excitement aside, I got questions:
 
 1. I'm proposing to proliferate the concept of immutability from 'this'
reference to a stack frame pointer. Although I'm confident it makes sense as
these are interchangeable in delegates, I could use some criticism to either
reject or strengthen the idea.
 
 2. What about delegates? Should there be means to express a "delegate that
doesn't mutate through its 'this'/stack frame pointer"? What should the syntax
be for defining such delegate type and for lambdas (delegate literals)?
 
 3. (vaguely related) Should there be means to express annotated delegates in
general (e.g. pure, nothrow).
There is. int delegate(int) pure square = cast( int delegate(int z) pure ) (int z) { return z*z; };
 What about annotated lambdas?
See the line above -- at the moment it requires a cast. Yuck yuck yuck. The other option would be for the compiler to automatically determine purity. I believe it always has access to the source code of the lambda, so there should be no problem to determine purity.
Jan 07 2011
parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Fri, 07 Jan 2011 17:18:39 -0500, Don <nospam nospam.com> wrote:

 Tomek Sowin'ski wrote:
 A while ago I pointed out that the result of an immutably pure function  
 (all arguments immutable, doesn't mutate globals) can be safely  
 converted to immutable. More here:
 http://d.puremagic.com/issues/show_bug.cgi?id=5081
  It helps with building complex immutable structures. Problem is,  
 virtually every construction site is different so one is forced to  
 define a new initializer function every time. To illustrate:
  void main() {
     immutable Node* init_leaf = ... ;
     uint breadth = ... ;
     immutable Node* tree = grow_tree(init_leaf, breadth);
 }
  Node* grow_tree(immutable Node* init_leaf, uint breadth) pure {
     Node* root = new Node;
     foreach (0..breadth) {
         Node* leaf = new Node(init_leaf);
         leaf.parent = root;
         root.leaves ~= leaf;
     }
     return root;
 }
  I tried to find a way to create ad-hoc functions conveniently.  
 Naturally, I turned to nested functions:
  void main() {
     immutable Node* init_leaf = ... ;
     uint breadth = ... ;
         Node* grow_tree() pure immutable {
         Node* root = new Node;
         foreach (0..breadth) {
             Node* leaf = new Node(init_leaf);
             leaf.parent = root;
             root.leaves ~= leaf;
         }
         return root;
     }
         immutable Node* tree = grow_tree();
 }
  Nested functions to be immutably pure must also guarantee that nothing  
 gets mutated through its stack frame pointer. But there's a problem --  
 the compiler won't accept 'immutable' on a nested function. I think it  
 should -- just like an immutable member function (e.g. in a class) is  
 allowed to play only with immutable members of that class, an immutable  
 nested function should be allowed to play only with the immutable  
 members of the stack frame. It may seem a lesser change but I'm pretty  
 excited as it solves the long-standing problems with immutable  
 structure initialization.
  Excitement aside, I got questions:
  1. I'm proposing to proliferate the concept of immutability from  
 'this' reference to a stack frame pointer. Although I'm confident it  
 makes sense as these are interchangeable in delegates, I could use some  
 criticism to either reject or strengthen the idea.
  2. What about delegates? Should there be means to express a "delegate  
 that doesn't mutate through its 'this'/stack frame pointer"? What  
 should the syntax be for defining such delegate type and for lambdas  
 (delegate literals)?
  3. (vaguely related) Should there be means to express annotated  
 delegates in general (e.g. pure, nothrow).
There is. int delegate(int) pure square = cast( int delegate(int z) pure ) (int z) { return z*z; };
 What about annotated lambdas?
See the line above -- at the moment it requires a cast. Yuck yuck yuck. The other option would be for the compiler to automatically determine purity. I believe it always has access to the source code of the lambda, so there should be no problem to determine purity.
Umm... int delegate(int) pure square = delegate(int z) pure { return z*z; }; compiles and runs fine. What doesn't compile is (int z) pure { return z*z; }; or anything similar.
Jan 07 2011
parent Tomek =?ISO-8859-2?Q?Sowi=F1ski?= <just ask.me> writes:
Robert Jacques napisa=B3:

  3. (vaguely related) Should there be means to express annotated =20
 delegates in general (e.g. pure, nothrow). =20
There is. int delegate(int) pure square =3D cast( int delegate(int z) pure ) (int z) { return z*z; }; =20
 What about annotated lambdas? =20
See the line above -- at the moment it requires a cast. Yuck yuck yuck. The other option would be for the compiler to automatically determine =
=20
 purity. I believe it always has access to the source code of the lambda=
, =20
 so there should be no problem to determine purity. =20
=20 Umm... int delegate(int) pure square =3D delegate(int z) pure { return =
=20
 z*z; }; compiles and runs fine. What doesn't compile is (int z) pure { =20
 return z*z; }; or anything similar.
Hm, you're right. Also, the argument list seems to be required with annotat= ions: int delegate() pure dg =3D delegate pure { return 0; }; // error int delegate() pure dg =3D delegate() pure { return 0; }; // compiles BTW, the syntax landscape seems densely populated, I wonder if demanded con= structs like pure { } wouldn't confuse the parser in some corner-case. It's all just syntax, though. I'm glad there's any way to get the job done. --=20 Tomek
Jan 08 2011