www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Re: const, final, scope function parameters

reply Robert Fraser <fraserofthenight gmail.coim> writes:
Heh; this is quite the interesting thread, though I seem to feel quite
different than most of you. I agree most function parameters should be final,
but const, and especially scope, just seem very wrong to me to be a default.
Maybe it's my Java background, but there are quite a few times I want users to
pass in reference to classes that implement a particular interface, or
otherwise have some sort of "setter" function for reference types, and having
to rewrite all my existing code to somehow allow that just seems like a total
waste.

Also, think about all the copying that would have to be made if scope was the
default - if I was passing in references to big objects into a collection, I
sure as hell wouldn't want a copy of each one made... one of D's advantages is
runtime performance. I know that scope could be turned off, but if on was the
default, a lot of coders would be more likely to use it, so I'd be much more
worried about using someone else's code for a performance-critical task.

In an ideal wold, most functions would indeed be "pure" functions, and if your
code has a lot of math going on or something, I can surely see that being the
case. However, real code is ugly, and programmers do break rules, usually
knowing full-well what we're doing. Reassigning a parameter does occasionally
introduce bugs, but it's a useful feature that we've come to accept and use
when it should be used, and avoid when it shouldn't be. I'm willing to put "in"
on API functions, and functions I expect others to use, but for private methods
and internal functions, let me code it how I want, and how I expect it to work,
without having to think about what I'm going to do with every parameter.

Eek, on re-reading, that came off a bit combative.... Sorry... I'll retreat to
my Virtual-Machine-managed world, and let you systems-level coders duke this
one out.

All the best,
Fraser

Walter Bright Wrote:

 It looks like making "const final scope" be the default for function 
 parameters is going to be infeasible. The troubles are that:
 
 1) It seems to knock a lot of people for a loop, who will be assuming 
 that an undecorated name would be like an undecorated name for a local 
 or global variable.
 
 2) Having to turn off one of the const, final, or scope, introduces the 
 need for some sort of "not" keyword, like mutable, !const, !final, etc. 
 It comes off looking bizarre.
 
 However, making "in" be equivalent to "const final scope" does seem to 
 work fine, requires no new keywords, and doesn't seem to confuse anyone.
 
 On a related note, "cstring" has received universal condemnation <g>, so 
    I'll just have to make "string" work.

May 28 2007
next sibling parent Myron Alexander <someone somewhere.com> writes:
Robert Fraser wrote:
 Heh; this is quite the interesting thread, though I seem to feel
 quite different than most of you. I agree most function parameters
 should be final, but const, and especially scope, just seem very
 wrong to me to be a default. Maybe it's my Java background, but there
 are quite a few times I want users to pass in reference to classes
 that implement a particular interface, or otherwise have some sort of
 "setter" function for reference types, and having to rewrite all my
 existing code to somehow allow that just seems like a total waste.
 
 Also, think about all the copying that would have to be made if scope
 was the default - if I was passing in references to big objects into
 a collection, I sure as hell wouldn't want a copy of each one made...
 one of D's advantages is runtime performance. I know that scope could
 be turned off, but if on was the default, a lot of coders would be
 more likely to use it, so I'd be much more worried about using
 someone else's code for a performance-critical task.

I have changed my mind and now think that Walter is right. I originally thought that const meant invariant but now know that you can rebind a const reference to a non-const variable and affect changes. In my original assumption, a const value would not incur performance hit with scope as the optimizer could make assumptions and no copy would be necessary. I now know this to be incorrect. Even though I prefer all parameters to be, at least, final, this would mean I am imposing my methodology on others and D is not supposed to be about enforcing a religion ;) I guess I am going to have to accept that there will be libraries/code that are almost impossible to read, or have undocumented/unintended side-effects. This is something that Darwinian forces will have to sort out. This was the reason I left C/C++ and joined the Java circus; well, that and I hate #define, #import :) On a side note, I originally considered D because I saw C+=1 done right, and with modules instead of source import. Of course, since then, the other lush goodies have their warm place in me heart.
 
 In an ideal wold, most functions would indeed be "pure" functions,
 and if your code has a lot of math going on or something, I can
 surely see that being the case. However, real code is ugly, and
 programmers do break rules, usually knowing full-well what we're
 doing. Reassigning a parameter does occasionally introduce bugs, but
 it's a useful feature that we've come to accept and use when it
 should be used, and avoid when it shouldn't be. I'm willing to put
 "in" on API functions, and functions I expect others to use, but for
 private methods and internal functions, let me code it how I want,
 and how I expect it to work, without having to think about what I'm
 going to do with every parameter.

I agree with you 100%.
 
 Eek, on re-reading, that came off a bit combative.... Sorry... I'll
 retreat to my Virtual-Machine-managed world, and let you
 systems-level coders duke this one out.
 

I don't think you presented a combative tone, just laying it down like it is.
 All the best, Fraser
 
 Walter Bright Wrote:
 
 It looks like making "const final scope" be the default for
 function parameters is going to be infeasible. The troubles are
 that:
 
 1) It seems to knock a lot of people for a loop, who will be
 assuming that an undecorated name would be like an undecorated name
 for a local or global variable.
 
 2) Having to turn off one of the const, final, or scope, introduces
 the need for some sort of "not" keyword, like mutable, !const,
 !final, etc. It comes off looking bizarre.
 
 However, making "in" be equivalent to "const final scope" does seem
 to work fine, requires no new keywords, and doesn't seem to confuse
 anyone.
 
 On a related note, "cstring" has received universal condemnation
 <g>, so I'll just have to make "string" work.


Regards, Myron.
May 28 2007
prev sibling parent reply Regan Heath <regan netmail.co.nz> writes:
Robert Fraser Wrote:
 Heh; this is quite the interesting thread, though I seem to feel quite
different than most of you. I agree most function parameters should be final,
but const, and especially scope, just seem very wrong to me to be a default.
Maybe it's my Java background, but there are quite a few times I want users to
pass in reference to classes that implement a particular interface, or
otherwise have some sort of "setter" function for reference types, and having
to rewrite all my existing code to somehow allow that just seems like a total
waste.

I think you may have a missunderstanding of one or more of the terms and the changes Walter suggested. If I'm wrong feel free to ignore my reply, or read it out of interest only ;) I don't think a re-write would be necessary in the case you describe above, just removal of 'const' from the input parameter (as you intend to call methods which modify the data to which the refernece refers).
 Also, think about all the copying that would have to be made if scope was the
default - if I was passing in references to big objects into a collection, I
sure as hell wouldn't want a copy of each one made... 

But that's already what happens. When you pass anything to a function a copy is made (unless you use 'ref' or 'out'). The important thing to realise is _what_ is copied. In the case of a value type the entire value type is copied, but, in the case of a reference/array only the reference is copied. The data to which a reference refers is never copied and 'scope' does not change that.
one of D's advantages is runtime performance. I know that scope could be turned
off, but if on was the default, a lot of coders would be more likely to use it,
so I'd be much more worried about using someone else's code for a
performance-critical task.

'scope' poses no performance problems as it doesn't change the existing behaviour, it is actually just a formalisation of existing behaviour. Take this example: class A { ..large object, lots of members.. } void foo(A b) {} A a = new A(); foo(a); when foo is called a copy of the reference 'a' is made and it is called 'b'. It is 'scope' because it exists solely in the function scope, once the function returns 'b' ceases to exist (because it was a copy of 'a' on the stack and the stack is reclaimed at function exit). This is what currently happens in D, and is what happens in C and C++ too. 'scope' is intended to detact and prevent this: A* pa; void foo(A b) { pa = &b; } In the above pa is set to the address of the reference 'b', this address becomes invalid when 'foo' returns and thus violates 'scope'. Likewise this: A* foo(A b) { return &b; } will be caught and prevented. This above code is currently valid D and the compiler gives no errors, once 'scope' is added to D the compiler will detect this and give an error. As for the others... 'final' means you cannot reassign the reference/array parameter during the function call. eg. void foo(A b) { b = new A(); } //voilates 'final' Initially you might not think this re-assign was dangerous and needed preventing, after all 'b' is a copy and changing it does not affect the original reference 'a' in any way. However, in a large function re-using an input parameter can introduce hard to find bugs, especially if 2+ programmers are working on the same code. Lastly 'const'. I think this one is the most important, especially given that arrays data is referenced. By this I mean when you call 'foo' here: void foo(char[] b) { b[0] = 'a'; } 'b' may be a copy of the original array reference 'a', but they both refer to the same array data and therefore changes to that data using either reference affect the other. This behaviour is often called 'aliasing' as 'b' is effectively an alias of 'a', changes via 'b' are the same as changed via 'a'. This is a source of many bugs and providing some protection, and by default, will prevent a large number of 'aliasing' bugs. Wow, that became a novel almost, sorry. Regan
May 28 2007
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Ah, thanks, now I understand scope (I understood the other two; I thought scope
would prevent copies of references). Still, const by default seems a bit odd -
make it explicit. For example, if a file stream is being passed to a function,
could I write to it inside the function if it was const?

Regan Heath Wrote:

 Robert Fraser Wrote:
 Heh; this is quite the interesting thread, though I seem to feel quite
different than most of you. I agree most function parameters should be final,
but const, and especially scope, just seem very wrong to me to be a default.
Maybe it's my Java background, but there are quite a few times I want users to
pass in reference to classes that implement a particular interface, or
otherwise have some sort of "setter" function for reference types, and having
to rewrite all my existing code to somehow allow that just seems like a total
waste.

I think you may have a missunderstanding of one or more of the terms and the changes Walter suggested. If I'm wrong feel free to ignore my reply, or read it out of interest only ;) I don't think a re-write would be necessary in the case you describe above, just removal of 'const' from the input parameter (as you intend to call methods which modify the data to which the refernece refers).
 Also, think about all the copying that would have to be made if scope was the
default - if I was passing in references to big objects into a collection, I
sure as hell wouldn't want a copy of each one made... 

But that's already what happens. When you pass anything to a function a copy is made (unless you use 'ref' or 'out'). The important thing to realise is _what_ is copied. In the case of a value type the entire value type is copied, but, in the case of a reference/array only the reference is copied. The data to which a reference refers is never copied and 'scope' does not change that.
one of D's advantages is runtime performance. I know that scope could be turned
off, but if on was the default, a lot of coders would be more likely to use it,
so I'd be much more worried about using someone else's code for a
performance-critical task.

'scope' poses no performance problems as it doesn't change the existing behaviour, it is actually just a formalisation of existing behaviour. Take this example: class A { ..large object, lots of members.. } void foo(A b) {} A a = new A(); foo(a); when foo is called a copy of the reference 'a' is made and it is called 'b'. It is 'scope' because it exists solely in the function scope, once the function returns 'b' ceases to exist (because it was a copy of 'a' on the stack and the stack is reclaimed at function exit). This is what currently happens in D, and is what happens in C and C++ too. 'scope' is intended to detact and prevent this: A* pa; void foo(A b) { pa = &b; } In the above pa is set to the address of the reference 'b', this address becomes invalid when 'foo' returns and thus violates 'scope'. Likewise this: A* foo(A b) { return &b; } will be caught and prevented. This above code is currently valid D and the compiler gives no errors, once 'scope' is added to D the compiler will detect this and give an error. As for the others... 'final' means you cannot reassign the reference/array parameter during the function call. eg. void foo(A b) { b = new A(); } //voilates 'final' Initially you might not think this re-assign was dangerous and needed preventing, after all 'b' is a copy and changing it does not affect the original reference 'a' in any way. However, in a large function re-using an input parameter can introduce hard to find bugs, especially if 2+ programmers are working on the same code. Lastly 'const'. I think this one is the most important, especially given that arrays data is referenced. By this I mean when you call 'foo' here: void foo(char[] b) { b[0] = 'a'; } 'b' may be a copy of the original array reference 'a', but they both refer to the same array data and therefore changes to that data using either reference affect the other. This behaviour is often called 'aliasing' as 'b' is effectively an alias of 'a', changes via 'b' are the same as changed via 'a'. This is a source of many bugs and providing some protection, and by default, will prevent a large number of 'aliasing' bugs. Wow, that became a novel almost, sorry. Regan

May 28 2007
parent Regan Heath <regan netmail.co.nz> writes:
Denton Cockburn Wrote:
 On Mon, 28 May 2007 18:56:00 -0400, Robert Fraser wrote:
 
 Ah, thanks, now I understand scope (I understood the other two; I thought
scope would prevent copies of references). Still, const by default seems a bit
odd - make it explicit. For example, if a file stream is being passed to a
function, could I write to it inside the function if it was const?
 

No, not if it changes the internals of the object. If you want that, then simply specify the parameter as 'ref' void foo(ref Stream x) { ...blah... } or void foo(scope final Stream x) { ...blah... } I'm trying to understand this too, so hopefully I didn't just tell you the wrong thing.

You're answer looks good to me. Passing with 'ref' means that the Stream 'x' is _not_ a copy of the passed reference, but is actually the exact reference passed. This is useful when you might want to re-assign it inside the function and expect that change to be reflected outside the function. Passing with 'scope final' gives a copy of the passed reference, 'final' prevents/detects you from reassigning it (catching the bug where you want the change to be reflected and actually meant to use 'ref' and preventing the bug where a parameter is re-used several times in a large function and this can result in bugs due to programmers expecting it to have it's inital value). 'scope' prevents/detects any attempt to store it's address in an external variable (which would be an ugly bug to find) because as a copy it ceases to exist after the function returns. I'm just re-iterating the behaviour for anyone still coming to grips with this. A good understanding of what goes on in the background (function agruments being copies, or not, etc) makes for better programmers. Regan
May 29 2007
prev sibling next sibling parent Denton Cockburn <diboss hotmail.com> writes:
On Mon, 28 May 2007 18:56:00 -0400, Robert Fraser wrote:

 Ah, thanks, now I understand scope (I understood the other two; I thought
scope would prevent copies of references). Still, const by default seems a bit
odd - make it explicit. For example, if a file stream is being passed to a
function, could I write to it inside the function if it was const?
 

No, not if it changes the internals of the object. If you want that, then simply specify the parameter as 'ref' void foo(ref Stream x) { ...blah... } or void foo(scope final Stream x) { ...blah... } I'm trying to understand this too, so hopefully I didn't just tell you the wrong thing.
May 29 2007
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Regan Heath wrote:
 
 'scope' poses no performance problems as it doesn't change the existing
behaviour, it is actually just a formalisation of existing behaviour.  Take
this example:
 
 class A { ..large object, lots of members.. }
 void foo(A b) {}
 A a = new A();
 foo(a);
 
 when foo is called a copy of the reference 'a' is made and it is called 'b'. 
It is 'scope' because it exists solely in the function scope, once the function
returns 'b' ceases to exist (because it was a copy of 'a' on the stack and the
stack is reclaimed at function exit).
 
 This is what currently happens in D, and is what happens in C and C++ too.
'scope' is intended to detact and prevent this:
 
 A* pa;
 void foo(A b) { pa = &b; }
 
 In the above pa is set to the address of the reference 'b', this address
becomes invalid when 'foo' returns and thus violates 'scope'.  Likewise this:
 
 A* foo(A b) { return &b; }
 
 will be caught and prevented.
 
 This above code is currently valid D and the compiler gives no errors, once
'scope' is added to D the compiler will detect this and give an error.
 

It should be noted however that it is not the value of b that is intrisincally 'scope'. It is the value of &b that is scope. The same happens with any local variable, parameter or not. -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 30 2007
parent Regan Heath <regan netmail.co.nz> writes:
Bruno Medeiros Wrote:
 Regan Heath wrote:
 
 'scope' poses no performance problems as it doesn't change the existing
behaviour, it is actually just a formalisation of existing behaviour.  Take
this example:
 
 class A { ..large object, lots of members.. }
 void foo(A b) {}
 A a = new A();
 foo(a);
 
 when foo is called a copy of the reference 'a' is made and it is called 'b'. 
It is 'scope' because it exists solely in the function scope, once the function
returns 'b' ceases to exist (because it was a copy of 'a' on the stack and the
stack is reclaimed at function exit).
 
 This is what currently happens in D, and is what happens in C and C++ too.
'scope' is intended to detact and prevent this:
 
 A* pa;
 void foo(A b) { pa = &b; }
 
 In the above pa is set to the address of the reference 'b', this address
becomes invalid when 'foo' returns and thus violates 'scope'.  Likewise this:
 
 A* foo(A b) { return &b; }
 
 will be caught and prevented.
 
 This above code is currently valid D and the compiler gives no errors, once
'scope' is added to D the compiler will detect this and give an error.
 

It should be noted however that it is not the value of b that is intrisincally 'scope'. It is the value of &b that is scope. The same happens with any local variable, parameter or not.

True, which is why I look at 'scope' as less of a "new feature" and more of a "formalisation" of what actually happens plus some help from the compiler at finding and preventing bugs related to it. In a sense all variables at whatever level are 'scope', that is to say they exist within a given scope (program, class, function) and cease to exist above/outside that scope. Also as you say, it's the address of them, rather than their value which is what becomes invalid when execution leaves their scope. Regan
May 30 2007