August 17, 2007
Walter Bright wrote:
> That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.

It frustrates me that people keep conflating what are orthogonal questions, such as:
  - Should we allow copy-assignments of class types?
  - Should we allow classes on the stack?
  - Should we use '*' to declare pointers-to-classes?

Those are 3 *VERY* different questions.  IMHO, we should disallow copy-assignments of class types (compiler detects the problem and spits out a clear error message, thus trivially educating the beginners), but still require the '*'.
August 17, 2007
Bill Baxter wrote:
> Jason House wrote:
>> Bill Baxter wrote:
>>> I'm starting to seriously wonder if it was a good idea to hide the pointers to classes.  It seemed kinda neat when I was first using D that I could avoid typing *s all the time.  But as time wears on it's starting to seem more like a liability.
>>
>>
>> The simple distinction between reference types and value types has irked me from the very beginning.  As a programmer using something that somebody else wrote, I shouldn't have to know its storage type. Distinctions between the two pop up here and there throughout D code. For example, value_type[] and ref_type[] have completely different copy behaviors.
>>
>>
>>>
>>> Bad points:
>>> - Harder to tell whether you're dealing with a pointer or not
>>>   (c.f. the common uninitialized 'MyObject obj;' bug)
>>> - To build on the stack, have to use 'scope'
>>>
>>> Good points:
>>> + No need to type '*' everywhere when you use class objects
>>> + ??? anything else ???
>>
>>
>> I was thinking recently of an interesting syntax twist...  Always require & when trying to get to an address, and use the raw variable name and "." to refer to whatever is pointed to.  Obtaining an address would require &.
>>
>> It's an interesting change, but would likely be too confusing to switch over.  Somehow I bet even suggesting the idea will mark me as a crack pot :)
> 
> Well realistically none of this is likely to change anyway, so you're free to suggest anything you want.  :-)
> I was thinking about something like that as well, though.  But couldn't really think how to make it useful.
> 
> My thinking was that in D we have classes sort of "shifted" by one from structs
> 
>         pointer-to-pointer   pointer   value
> struct    &(&p)                &p        p
> struct*    &p                   p       *p
> class      &p                   p       N/A
> 
> So instead of that make structs default to pointers too, to shift everything back to be the same:
> 
>         pointer-to-pointer   pointer   value
> struct#       &(&p)            &p        p
> struct        &p                p       *p
> class         &p                p       N/A
> 
> '#' (or whatever -- perhaps re-use 'scope') becomes the "by value" indicator.  So to do a struct-by-value you'd declare:
> 
>   MyStruct# Foo;
> 
> while
>   MyStruct Bar;
> 
> would be a pointer/reference just like MyClass is.
> 
> Some problems are:
> - what do you do about built-in value types like 'int'?  Doesn't make sense to make 'int x' be a pointer type, IMHO.  And if the built-in types behave differently from structs have you gained much?
> - for structs, as-value should be the common case, so you'll have to use a '#' most every time you use a struct.
> - it still won't make the struct -> class transition all that much easier unless classes gain the ability to be passed around by value.

It also doesn't help clarity (completely) because you can still have typedefs and other user-defined types that would have value semantics.
August 17, 2007
"eao197" <eao197@intervale.ru> wrote in message news:op.tw7obrzwsdcfd2@eao197nb2.intervale.ru...
> On Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb@nowhere.com> wrote:
>
>> Where as with the slicing problem anything can happen and it's incredibly hard to debug.
>
> Any examples? What's bad and dangerous happens in the case of slicing?

Never mind, i had misunderstood what the slicing problem was. Sorry.

jb


August 17, 2007
On Fri, 17 Aug 2007 21:46:16 +0400, Jb <jb@nowhere.com> wrote:

> "eao197" <eao197@intervale.ru> wrote in message
> news:op.tw7obrzwsdcfd2@eao197nb2.intervale.ru...
>> On Fri, 17 Aug 2007 18:58:19 +0400, Jb <jb@nowhere.com> wrote:
>>
>>> Where as with the slicing problem anything can happen and it's incredibly
>>> hard to debug.
>>
>> Any examples? What's bad and dangerous happens in the case of slicing?
>
> Never mind, i had misunderstood what the slicing problem was. Sorry.

No problem. It is good that D has less problems in language than C++. D hasn't slicing problem -- it is good.

But having values, pointers and references (which can be used only with classes) seems too overcomplicated for me. And if we would have only values and pointes it could make simple solutiton for null-pointers problem (may be not simple, because Nice has some bugs, but more simple that in case of references, I think).

-- 
Regards,
Yauheni Akhotnikau
August 17, 2007
Russell Lewis wrote:
> Walter Bright wrote:
>> That's known as the 'slicing' problem. It's pernicious in that it can be extremely hard to expose via testing or code reviews, yet will expose the program to unpredictable behavior.
> 
> It frustrates me that people keep conflating what are orthogonal questions, such as:
>   - Should we allow copy-assignments of class types?
>   - Should we allow classes on the stack?
>   - Should we use '*' to declare pointers-to-classes?
> 
> Those are 3 *VERY* different questions.  IMHO, we should disallow copy-assignments of class types (compiler detects the problem and spits out a clear error message, thus trivially educating the beginners), but still require the '*'.

And I assume you would still allow classes on the stack in that case.
Good point.  That way you would retain uniform struct/class syntax, but still avoid the slicing problem.

The only down side I can think of is what someone mentioned before. Since you can't use operator overloading on pointers, it would mean either
1) you suffer through lots of dereferencing (*a + *b), or
2) you need to introduce a full-fledged C++-like reference type (a pointer that acts like a value).  Or
3) you require a sigil to do all pointer math.

More about 3):
I was tempted to to say "only for pointer-to-struct/class" but that interferes with the ability for class/struct to mimic built-in syntax. E.g. with this pattern:
   for (T* p=&TArray[0]; p!=end; p++) {...}
you don't want to have to do something different depending upon what T is.  With the approach 3) you'd have to make that something like
   for (T* p=&TArray[0]; p!=end; as_ptr(p)++) {...}

Maybe you could use # for the sigil instead of as_ptr().  It has an intuitive meaning of "treat this as a simple number" instead of something more complex:
   for (T* p=&TArray[0]; p!=end; #p++) {...}

That doesn't seem half bad to me, but it would certainly be annoying for code that does lots of pointer math.

--bb
August 17, 2007
James Dennett wrote:
> Walter Bright wrote:
>> C++ is loaded with idioms and conventions to try and head off major
>> problems. I'd rather snip off such problems at the source - for one
>> reason, it will dramatically reduce the learning curve of the language.
>> For another, the more guarantees the language can offer, the lesser a
>> burden it is on the code auditor, and the more likely the code is to be
>> correct.
> 
> Valid points, though removing expressive power tends to move
> the complexity into code, and it's hard to remove the power
> to write bad code without also removing the power to write
> great code.

The counterexample I'd give is that D classes tend to be implementable with much less complex code than C++ classes.
August 17, 2007
James Dennett wrote:
> Walter Bright wrote:
>> James Dennett wrote:
>>> Walter Bright wrote:
>>>> Value types are fundamentally different from reference types. D gives
>>>> you the choice.
>>> C++ gives you *more* choice by allowing easier migration between
>>> the two without imposing performance or syntactic differences.
>> That isn't my experience. In my work on DMDscript in D, I changed some
>> types between struct and class to try out some variations, and found it
>> to be a lot easier than in C++. For one thing, I didn't have to find and
>> edit all the -> and .'s.
> 
> In C++, no, you didn't, as structs and classes are no different
> in their use of "->" and "." (as you're aware, they're all just
> classes).

I meant I switched between value and reference semantics for an object.
August 18, 2007
Tristam MacDonald Wrote:

> Agreed, but I can't think of another language that supports this definition of OOP (apart from 'mutable' under C++ - which is widely regarded as a hack).
> 
> Alex Burton wrote:
> > By making all objects that an object has references to necessarily part of that object, the transitive const feature means that the only state relationship a class can have to another class is encapsulation.
> > 

And I totally agree that mutable is a hack.
But when a class has a *pointer* to another object in C++, I can choose whether to tell the compiler that the other object is part of the class or not.
In D all classes are referred to by pointers. And all pointers are assumed to mean part of (because of transitive const).

It is perfectly logical to disallow changing objects that are part of a const class.
I am having trouble figuring out how I am going to write code when I can't store a pointer to something in a class without it being interpreted as a part of relationship.

Clearly in the below code transitive const does not make sense. And does not allow me to describe reality. Which is what I need to do to write sensible code.

class Table
{
   Database mDatabase;
   const const(Data) getData()
   {
      mDatabase.lock();     //error cannot change the state of mDatabase even though it can be in no way thought of as part of Table
      ...
      mDatabase.unlock();
   }

};
August 18, 2007
I'm sorry for my agressive behaviour, I was wrong.

On Fri, 17 Aug 2007 09:25:34 +0400, eao197 <eao197@intervale.ru> wrote:

> I think if there was only one reference/pointer type in D than it would be easier to add such non-null references into the language. For example, it is possible to use another symbol for pointers:
>
> class C {};
> C # non_null = ...; // Can't be null.
> C * nullable = ...; // Can be null.
>
> void f( C # non_null_arg ) { ... } // non_null_arg can't be null.
>
> non_null = nullable; // Error! Checked by compiler.
> f( nullable ); // Error! Checked by compiler.
> nullable = non_null; // Ok.
> f( non_null ); // Ok.
> if( nullable !is null )
>    f( nullable ); // Ok. In this branch nullable is not null.
>

I totally forgot about operator overloading. So it is obviously necessary to have not only values and pointers, but also references.

I think that if introduce special syntax for references than problem for non-null references could be solved. Moreover with explicitely defined references it is possible to remove 'in'/'out' argument modifiers.

For example, let use '@' as nullable reference and '#' as non-null reference:

int i; // Value.
int @ i; // Nullable reference to int.
int # i = ...; // Non-null reference to int. Must be initialized.
int * p; // Nullable pointer to int.

struct S;
S s; // Value.
S @ s;
S # s = ...;
S * s;

class C;
C c; // Error! C is a reference type.
C @ c;
C # c = ...;
C * c;

// Various kinds of arguments.
void f(
	// Pass by value.
	S a1,
	// Pass by reference, like 'out' argument.
	S @ a2,
	// Pass by reference, like 'in' argument.
	const(S) @ a3,
	// Same as two previous, but require non-null references.
	S # a4,
	const(S) # a5,
	// Pass by pointer, like 'out' argument.
	S * a6,
	// Pass by pointer, like 'in' argument.
	const(S) * a7 );

I don't think that non-null pointer has much sence. And if we try to introduce non-null pointers we can end up with things like non-null pointer to nullable pointer to non-null pointer to something.

Unfortunately, syntax 'C @ c' is ugly in comparision with current 'C c'. But I hope it make type system more consistent -- it is easy to distinguish values form references in code.

-- 
Regards,
Yauheni Akhotnikau
August 18, 2007
Walter Bright wrote:
> James Dennett wrote:
>> Walter Bright wrote:
>>> C++ is loaded with idioms and conventions to try and head off major problems. I'd rather snip off such problems at the source - for one reason, it will dramatically reduce the learning curve of the language. For another, the more guarantees the language can offer, the lesser a burden it is on the code auditor, and the more likely the code is to be correct.
>>
>> Valid points, though removing expressive power tends to move the complexity into code, and it's hard to remove the power to write bad code without also removing the power to write great code.
> 
> The counterexample I'd give is that D classes tend to be implementable with much less complex code than C++ classes.

One counterpoint to which is that most complexity in large systems arises at larger scales than individual classes, and then a language that allows you to express more of the properties of a class tends to fare better "in the large".

But, as is sadly usual for our field, it's hard to provide robust objective data to support either viewpoint, and we're down to debating based on our (widely varying) experiences.

-- James