Const by Default

Derek Parnell derek at nomail.afraid.org
Sun Jun 24 19:14:05 PDT 2007


On Sun, 24 Jun 2007 18:02:42 -0700, David B. Held wrote:

A number of very good discussion points and I'm not sure if I am qualified
to respond, but fools rush in, as they say ... <g>

> I think an interesting point was brought up in the earlier CbD thread, 
> which is specifically the issue of c'tors, and generally, the issue of 
> passing mutable references.  So consider:
> 
> class MyObj
> {
>      this(MyClass a, MyClass b)
>      {
>          a_ = a;
>          b_ = b;
>      }
> private:
>      MyClass a_;
>      MyClass b_;
> }
> 
> With CbD, this code is incorrect.  Would you like to explain to a novice 
> programmer why?

Ok ... how about ... 

* Unless told otherwise, data (in objects) passed to a function can not be
modified.
* Unless told otherwise, data (in objects) local to the can be object can
be modified.
* Thus the assignment of the parameters to the private objects is
contradictory.

>  Now, let's try to fix it:
> 
>      this(ref MyClass a, ref MyClass b)
>      {
>          a_ = a;
>          b_ = b;
>      }
> 
> This code works, but now you have to explain to the student that even 
> though classes are always passed by reference in D, you have to actually 
> spell it out explicitly sometimes.

You are assuming current D syntax only. Another possibility is new syntax
...

      this(rw MyClass a, rw MyClass b)
      {
          a_ = a;
          b_ = b;
      }

where, for the *sake of this argument*, the keyword 'rw' is used to tell
the compiler and code reader that the code author is allowing the new
object read-write access to the passed object data.

The key to my thinking is that I'm distinguishing the passing mechanism
from the access permissions. The fact that it is passed by reference is
interesting but not the point of the exercise, which is to tell the
object/function what are its access limitations/permissions to passed data.
The compiler can determine the most efficient passing mechanism
independently of the access permissions.

>  And further note that the only 
> reason this form is not less efficient is because references to 
> references are collapsed to simple references.  Otherwise, there would 
> be an extra level of indirection here. 

Forget the parameter passing mechanics and instead concentrate on what the
code author is allowing the function/object to be able to do.

> Here's another try:
> 
>      this(final MyClass a, final MyClass b)
>      {
>          a_ = a;
>          b_ = b;
>      }
> 
> Of course, you also have to explain why a and b are marked 'final' when 
> they already are implicitly.  You have to tell the novice that you are 
> specifying 'final' to turn off 'const'.  I expect that to elicit a: 
> "Huh!??!" response.

So would I, and this is plainly a poor solution.

But what about ...

 class MyObj
 {
      this(MyClass a, MyClass b)
      {
          a_ = a;
          b_ = b;
      }
 private final invariant:
      // Can be assigned to once, but from then on neither the
      // reference nor its data can be modified.
      MyClass a_;
      MyClass b_;
 }

Or if that's not what the author intended ...

 class MyObj
 {
      this(MyClass a, MyClass b)
      {
          a_ = a.clone; // deep copy
          b_ = b.clone;
      }
 private:
      MyClass a_;
      MyClass b_;
 }

Or maybe that's not what the author intended either, so how about ...
 class MyObj
 {
      this(MyClass a, MyClass b)
      {
          a_ = a.dup; // shallow copy
          b_ = b.dup;
      }
 private:
      MyClass a_;
      MyClass b_;
 }

See how the using of implicit 'read-write' function parameters disguises
the author's intentions?

> Implicit actions are dangerous, which is a lesson we should have learned 
> in spades from C++.

Well, I'd say that they *can* be dangerous rather than every case is always
dangerous.

But in any case, we already have implicitness in D parameter passing,
namely that implicitly data access is read-write. In other words, if one
does not use 'const' and does not use 'invariant' then the default
(implicit) action is to allow read-write access. Are you saying that we
should not even have that?!

> Every day I have to deal with scary smart pointer 
> solutions that have each, in their infinite wisdom, exposed implicit 
> conversion to T*, and have bent over backwards to try to prevent the 
> types of programming mistakes that entailed.  I really don't see what is 
> so evil about putting 'in' on your arguments.

Nothing about putting 'in' in is evil and I think that nobody is actually
saying that either. 

The point is which is the better compromise in terms of making code cheaper
to write and maintain? 

If the default action is to allow "read-only" access it can be argued that
it helps the compiler (and code reader) to identify code that causes
side-effects by making such code explicitly say that's what it might be
doing. 

If the default action is "read-write" it increases the effort to determine
if a function is actually changing data or not; one has to read the
function code to make the assessment rather than just look at its
signature. 

Now I know this does not yet apply to accessing public or package
identifiers but it does help with know how a function might handle the data
passed to it.

> There are a few other places where the absence of something means 
> something.  Member functions, for instance.  You don't declare the 
> 'this' parameter, as it is implicit.  And yes, you can turn it off with 
> 'static'.  But it's not like member functions are declared as:
> 
>      member static int foo();
> 
> and spelling out:
> 
>      static int foo();
> turns off the 'member' attribute.  That would be silly.  

This is a good case in point about the appropriateness of keyword
semantics. I would like to see a more explicit and obvious method of
identifying class-scoped functions. For example, something akin to:

   scope(class) int foo();  // instead of the overloaded 'static'.

> Another place 
> where implicit stuff goes on is builtin type promotion.  Think signed 
> vs. unsigned.  Who thinks the current rules are A Good Thing(TM) and 
> cannot possibly be improved?

Not me.
 
> At one time, C had 'implicit int' return types.  C++ removed them.  Why? 
>   Because 'implicit int' is A Bad Thing(TM).  C++ has implicit template 
> instantiation.  D doesn't.

Huh? I thought D does have a form of implicit template instantiation.

-- 
Derek
(skype: derek.j.parnell)
Melbourne, Australia
25/06/2007 11:32:57 AM



More information about the Digitalmars-d mailing list