auto, var, raii,scope, banana

Chad J gamerChad at _spamIsBad_gmail.com
Fri Jul 28 01:44:44 PDT 2006


Regan Heath wrote:
> 
...
> I've already said.. I think it's intuitive because it 'looks' like a 
> class  declared on the stack, as in C++ and like those it will be 
> destroyed at  the end of scope. Your counter argument is that not 
> everyone is a  C++ programmer, correct? In which case, read on..
> 
...
> 
> I come from a C background. I have done a little C++, a little Java and  
> very little C#.
> 
> 
> The absence of 'new' gives us the required information. You are 
> required  to know what the absense of 'new' _means_ just like you are 
> required to  know what keyword X means in any given context. In short, 
> some knowledge  of programming in D is required to program in D.

I agree, the point is to make it easier to obtain that knowledge while
learning.

> 
> To expand on why I think it's intuitive let look at 2 scenarios, first 
> the  C++ programmer trying D. Likely their first program involving 
> classes will  look like this:
> 
> class A {
>   int a;
> }
> 
> void main() {
>   A a = A();
>   writefln(a.a);
> }
> 
> currently, this crashes with an access violation. 

Actually, it doesn't compile.

main.d(6): function expected before (), not A of type main.A
main.d(6): cannot implicitly convert expression ((A)()) of type int to
main.A

Maybe not the best error messages for this case, but better than an
access violation.

> Add the new RAII 
> syntax  and not only does it not crash, but it behaves just like a C++ 
> program  would with A being destroyed at the end of scope.

I hear that such syntax, in C++, means that 'A' will be stack allocated,
which AFAIK does not imply RAII.  One example I can think of that would
break RAII is if the function gets inlined, then the stack allocated
object may not be deleted until some point outside of the scope's end.
It will /probably/ be deleted at the end of the function that the
enclosing function was inlined into.  I could be wrong about that though.

Also, does that C++ syntax allow class references to leak into code that
is executed after the class is deleted?

> 
> Now, take a Java/C# programmer, their first program with classes will be:
> 
> class A {
>   int a;
> }
> 
> void main() {
>   A a = new A();
>   writefln(a.a);
> }
> 
> No crash, and the behaviour they expect.
> 
> Then imagine the same programmers looking at each others code, the  C++ 
> programmer will understand 'new' to indicate heap allocation and the  
> Java/C# programmer will need to ask the C++ programmer what the absense 
> of  'new' means, he'll reply that the class is destroyed at the end of 
> scope.  He might also say it's stack allocated, but that's fine, both of 
> them have  something to learn, and it has no effect on the way the 
> program behaves.

That learning process would be nice, but it assumes an extra programmer
being around.  As I learn D, I am alone.  Either I go to the spec or I
ask on the newsgroup.  I'm not sure how many others are in the same
situation.

I've explained below why I still don't think it's very obvious, which
means more potential newsgroup asking.  That doesn't bug me too much
though.

> 
> Simple, intuitive and elegant no matter what your background. Of 
> course,  if you're a Java/C# programmer you have to 'learn' what the 
> absense of  'new' means. Both have to learn that D might not stack 
> allocate the RAII  class instance.
> 
> If we use a 'scope' keyword then both sets of programmers have some  
> learning to do. It's not much worse, but this makes it my 2nd choice.
> 

"Both have to learn that D might not stack allocate the RAII  class
instance."
"If we use a 'scope' keyword then both sets of programmers have some
learning to do."

If they both have learning to do anyways, why not just have them learn
'scope'?
I suppose it wouldn't be too bad otherwise, except for one problem:
The unfortunate C++ programmer's program worked as expected.

I say problem and unfortunate, because they may just assume that D
behaves like C++ in all of the corner cases, and if I'm correct about
the RAII vs stack alloc above, it may not.

>> For a D newbie reading code, having a keyword is good because it 
>> gives  them a keyword to search the spec for. Better yet if it is 
>> 'scope',  because that gives them an idea of where to look.  This is 
>> also one of  those things that is used seldom enough that the 
>> succinctness gained  from implicit things is only slightly helpful.
> 
> 
> The C++ programmer will already know what it does.
> The Java/C++ programmer will search for 'new' (because it's absent).
> In either case they'll find the docs on RAII at the same time as they 
> find  the docs on 'new'.

Searching for 'new' is a good idea.  What if they don't think of that?
The only reason I would associate the absence of 'new' with allocation
is because of the talk from C++ programmers on this newsgroup.
Otherwise it's some kind of wacky cool feature of D that they have not
read about yet (as it is now!).  This is why I prefer 'scope' - it would
make scope, unambiguously, THE keyword to search for to find out what's
going on.

> 
>> In a nutshell: assume a programmer of undefined language background, 
>> and  I believe 'scope' will be much more informative (and therefore  
>> intuitive) than a blank space.
> 
> 
> I've assumed programmers from 2 types of background above, I still 
> think  removing 'new' is better than adding 'scope'.
> 

That's fine as long as you clear up the two things:
- What tells a non-C++ coder to look up 'new'? (besides another coder)
- The possible assumption by a C++ coder that RAII syntax means stack
allocation.  (and subsequent whammy when they hit a pointy corner case)

Proving that there are no corner cases in 'stack allocation implies
RAII' or me being wrong about that meaning in C++ should clear up that
second one.

>>>
>>>   What's the point of using static opCall here, as opposed to a 
>>> normal   static method or even a free function?
>>
>>
>> Why not a normal static method? - implementation hiding.
> 
> 
> I'm assuming this defintion:
> http://en.wikipedia.org/wiki/Information_hiding
> 
> I'm not sure I follow your logic, did you mean "encapsulation" instead?
> 
> If you did mean "information hiding" then it seems to me that using 
> opCall  is a design decision, just as using a method called "Bob" would 
> be. The  advantage to using "Bob" is that you can define an 'stable' 
> interface eg.
> 
> interface MyImplementation {
>   int Bob(int x);
> }
> 
> and in that way protect your code against changes in implementation. In  
> short, this is a better "information hiding" solution than using static  
> opCall.
> 
>> Why not a free function? - I'm assuming you mean a normal function  
>> declared at module scope.
> 
> 
> Yes.
> 
>> It allows you to have multiple functions with access to the same  
>> non-static variables (member variables).
> 
> 
> What does?
> 
> _static_ opCall does *not* have access to non-static variables, it can  
> only access static variables. Static methods and variables are very  
> similar to module level variables. The difference being how to access 
> them  (class name prefix) and where they are declared (inside the class 
> instead  of at module level). You can replace static methods and 
> variables with  module level ones quite easily.
> 
>> I suppose this is usually accomplished with nested functions, or OOP  
>> that can't do implementation hiding.
> 
> 
> I'm obviously not following your point here.. can you elaborate, 
> perhaps  another example?
> 
>> Doing it this way allows me to avoid forward referencing issues
> 
> 
> What forward referencing issues does the code above have?
> 
>> and also gives me more options about how my code behaves.
> 
> 
> What options? example?
> 
>>> What problem does it solve?
>>
>>
>> Annoyance from forward referencing in nested functions, but that's 
>> just  one.  Maybe a small degree of implementation hiding for memory  
>> management, though I haven't tried this enough to demonstrate it.  It 
>> is  not really a solution.  It is a technique, a syntax sugar, or just 
>> an  easy way to get D to do things you would not ordinarily make it do.
> 
> 
> So far:
> - I'm not seeing the forward reference issue(s)
> - It seems like a worse way to implement information hiding (than an  
> interface and method).
> 
>>> What new technique or method does it allow?
>>
>>
>> Multiple functions using the same non-static variables with no need 
>> for  forward referencing and possible code behaviour tweaks, etc yadda 
>> yadda.    I admit it's not horribly important, but I don't think that 
>> this  choice of syntax is either.  Most of all, I like it.
> 
> 
> I *like* non-static opCall, I can see benefits to that. But, so far,  
> static opCall seems to be fairly useless to me.
> 
>> *sigh* those were long winded answers, sorry about that.
> 
> 
> No worries. Long winded tends to imply more information and that's not 
> a  bad thing.
> 
>>>> I do like using the scope keyword for this btw.  It seems quite   
>>>> consistant with D's meaning of 'scope'.
>>>
>>>   It's not bad but it's my 2nd choice currently.
>>>  Regan
>>
>>
>> Care to explain why static opCall is a hack and/or not useful outside 
>> of  RAII or struct constructors?
> 
> 
> I'm trying, by learning when, where and why you use it. I can't think of 
> a  place where _I_ would use it (which is why I think it's not useful).
> 
> It's only a 'hack' when used with a struct to emulate a constructor.  
> Strangely that 'hack' is the only vaguely good use I've seen for a  
> *static* opCall.
> 
> Regan

OK I think I have not explained my example very well.
First I'll simplify my original example and try harder to explain.  I've
added comments this time.
Here it is:

import std.stdio;

// class used as a function
class f
{
     int result;
     int tempVar1; // this is your non-static variable

     static int opCall( int x )
     {
         // what do you know, not so 'static' after all!
         f instance = new f(x);
         int result = instance.result;

         // cleanup
         delete instance;
         return result;
     }

     this( int x )
     {
         // If this were a function with nested functions,
         //   then the main execution would occur here.
         result = x + internalFunc();
     }

     // this is a non-static function
     int internalFunc()
     {
         return 314;
     }
}

void main()
{
     int x = 42;
     x = f(x);
     writefln(x);
}

Now I'll rewrite it into a free function:

import std.stdio;

int f( int x )
{
      // Error! internalFunc is not defined.
     return x + internalFunc();

     // oh but here it is... nested functions don't allow this
     int internalFunc()
     {
         return 314;
     }
}

void main()
{
     int x = 42;
     x = f(x);
     writefln(x);
}

In a trivial case like this all you have to do is move the nested
function internalFunc to the top.  In other cases though, where you want
to be able to places the functions where they intuitively belong, this
becomes annoying.  This is that forward referencing issue I was talking
about.

Also, if 'f' were derived from another class, then the code executed in 
f's constructor would have access to the super class's members.  That's 
more to do with OOP, but it's all hidden behind a static opCall.

I suppose you could use a module function to construct and delete a 
private class that gets used for it's own scope and inheritance.  I 
worry that I am missing some detail that stopped me from just doing that 
before.

Sean, perhaps you could share some of your uses for static opCall since 
I'm doing such a bad job at this?


Only slightly related - I wonder if D's 'new' even implies heap 
allocation as it is.  That might just be a dmd thing, but I am looking 
in the spec at Classes -> ctors/dtors, and it doesn't say where they get 
allocated.  Maybe someone who is good at searching the spec can find if 
it says where classes get allocated?
My thought, as xs0 has mentioned as well, is that 'new' could be defined 
independant of those implementation details.  For example, just say that 
'new' will allocate a class instance, and that the instance will be 
cleaned up only after there are no more references to that class.  The 
only exception being explicit deletion.  Since RAII gaurantees, AFAIK, 
that there are no references leaked into code that gets executed after 
the scope is exited, then stack allocation would work here.  I'm finding 
it hard to imagine code that would require an object to be in the heap's 
address range or in the stack's address range.  xs0 says there are other 
cases in which a class can be allocated on the stack without breaking 
the gaurantees of no premature deletion, and I wonder what they are.



More information about the Digitalmars-d mailing list