July 24, 2009
On Thu, Jul 23, 2009 at 3:21 PM, Walter Bright<newshound1@digitalmars.com> wrote:
> Rainer Deyke wrote:
>>    They're value types (non-copyable for the most part, but stored directly instead of as pointers, with RAII), but they're often passed around as polymorphic references.
>
> I.e. they're a mess. You just confused the heck out of me.

I think the case he's talking about is just a sort of optimization to use stack allocation instead of heap alloc.

void func()
{
    DerivedFoo x;
    callMethodThatTakesAnyFoo(&x);
}

I.e. the kind of thing you'd use "scope Foo" for in D.

Except that in the C++ case you could have DerivedFoo be part of another struct or class instead of being a local variable.

--bb
July 24, 2009
Walter Bright wrote:
> Rainer Deyke wrote:
>>   - Implementation inheritance of value types, even without actual
>> polymorphism, is useful.  See, for example, the curiously recurring
>> template pattern (and all of its uses).
> 
> You can do this with 'alias this'.

Composition instead of inheritance?  Doesn't work if I need virtual functions.

>>   - Polymorphic references to value types are often useful.  D has
>> references to value types (in the form of 'ref' parameters and pointers)
>> but these are not polymorphic.  As an example, I would name the standard
>> C++ iostream.
> 
> iostream isn't a good example of design <g>.

I fully agree, but /this particular aspect/ of iostreams isn't an example of bad design either.

How would you do this in D?  Turning iostreams into reference types
isn't the answer, especially if this means losing RAII.
Non-polymorphics iostreams is also no-go.  I suppose you could create a
polymorphic reference-type wrapper around a pointer to the actual
iostream struct, but that's a lot of extra work that isn't necessary in C++.

>>     They're value types (non-copyable for the most part, but stored
>> directly instead of as pointers, with RAII), but they're often passed
>> around as polymorphic references.
> 
> I.e. they're a mess. You just confused the heck out of me.

I don't see what's confusing about this.

Most iostreams are non-copyable, i.e. the following just plain doesn't
compile:
  stream_type a;
  stream_type b = a;

Iostreams are not reference types, at all.  It's not possible for one iostream to (accidentally or intentionally) alias another iostream. There are not idiomatically allocated with 'new'.  Therefore they avoid the problems of having reference types in the language.  Long distance bugs with iostreams are highly unlikely.

Iostreams are passed as references - real references, with the 'T&' syntax, equivalent to 'ref T' in D - because that's the only way to pass them around.  The callee always knows they are receiving a reference, because the '&' is an explicit part of the function signature.


>>   - C++ makes a clear distinction between a reference and the thing
>> being referenced.  Even if value types and polymorphism were mutually
>> exclusive, this distinction would be useful.  All types are consistently
>> treated as value types, even those types that reference another object.
>>
>>   I *like* having to write 'gc_ptr<Object> p;' instead of 'Object p;'.
>> I *like* having to write 'p->f();' instead of 'p.f();'.  It keeps my
>> code clearly and more explicit.
>>
>>   - C++ does not have separate rules for value types and reference
>> types.  All types are implicitly value types; values of all types can be
>> placed on the heap.  This simplifies the language by having just one set
>> of rules instead of separate rules for classes and structs.  Again, this
>> unification would be useful even if it was an error to declare a
>> variable of a polymorphic type as a direct variable.
> 
> That philosophy conflicts with using . for values and -> for references.

Are you deliberately trying to misunderstand me?

In C++, for any type T, there is a type T* (and shared_ptr<T> and inclusive_ptr<T> and so on).  T is a value type, always.  T* is a pointer to a value of type T, but T* itself is still a value type.

Given 'T* p;', 'p.x' accesses a member of 'p', while 'p->x' or '(*p).x' accesses a member of the value referenced by 'p'.  Always, consistently, whether 'T' itself is a primitive type, a class, or even itself a pointer type.

> The capability of being able to move value types is deliberately designed in. It's there for the obvious reason of being able to create a moving garbage collector, and for the more subtle reason of being able to optimize away many more cases of copy-construction and destruction.

I know.  Still, it invalidates valid C++ idioms.  If there was some way to say "this struct cannot be moved", or even a hook function that is called before/after/during each move, then those idioms might still be valid.


-- 
Rainer Deyke - rainerd@eldwood.com
July 24, 2009
On Thu, Jul 23, 2009 at 5:20 PM, Rainer Deyke<rainerd@eldwood.com> wrote:
> Walter Bright wrote:
> I don't see what's confusing about this.
>
> Most iostreams are non-copyable, i.e. the following just plain doesn't
> compile:
>  stream_type a;
>  stream_type b = a;
>
> Iostreams are not reference types, at all.  It's not possible for one iostream to (accidentally or intentionally) alias another iostream. There are not idiomatically allocated with 'new'.  Therefore they avoid the problems of having reference types in the language.

I don't get your meaning in that last sentence.  What's the problem of having reference types in the language?  C++ has reference types in the language -- T&.  If you just mean it's not possible to forget to deallocate on scope exit, then you just need to use "scope" in D to get that effect.  Or an explicit "scope(exit)" clause.

> Long distance
> bugs with iostreams are highly unlikely.
>
> Iostreams are passed as references - real references, with the 'T&' syntax, equivalent to 'ref T' in D - because that's the only way to pass them around.  The callee always knows they are receiving a reference, because the '&' is an explicit part of the function signature.

Seems like scope handles most of this just fine.  Except the case where you want to have an iostream as part of a larger class.  But then that larger class is probably going to need deterministic destruction too, so wherever it's used it should probably be scope'ed also.

--bb
July 24, 2009
Rainer Deyke wrote:
>>> The
>>> struct/class split, on the other hand, introduces many more problems
>>> that are harder to fix.
>> Again: what are those problems?
> 
> Syntactic ambiguity.  Confusion between an instance of a class and a
> reference to that instance.

I was initially thrown by this when I started some Java programming. But it soon became clear this was an advantage, and I came to prefer it.

So I think this is a case of one's perspective, not an actual problem.
July 24, 2009
On Thu, Jul 23, 2009 at 6:10 PM, Adam D. Ruppe<destructionator@gmail.com> wrote:
>
> What do you propose we do about the commas in loops? Breaking them would be a pretty big change to the C folks.

It's trivial to allow a comma-separated list of expressions in the for loop header.  If Pythonic tuple syntax were used (i.e. parens required), there would be no parsing ambiguity.
July 24, 2009
Ary Borenszweig wrote:
> Walter Bright wrote:
>> Michiel Helvensteijn wrote:
>>> * No control over their use by class designer: ANY member function with one
>>> or zero parameters may be called using 'property syntax'. This is not a
>>> good thing.
>>
>> Why not? Seriously, what is the semantic difference?
> 
> Semantic difference: a property doesn't have *visible* side effects. If you invoke it one hundred times, it should always return the same thing. And nothing else in your program should change. So it's kind of like pure functions.
> 
> I say "visible" because you might want to implement a property lazily. But the logic remains inside your class and it's visible in the outside world.

Ok, but not having visible side effects is just a convention, one which is apparently not enforced by languages with special property syntax.

A pure function, however, can be statically guaranteed to have no side effects. So IDEs, etc., can regard as 'properties' any such methods which take no arguments.
July 24, 2009
Leandro Lucarella wrote:
> The problem is, if is hard to write, or ugly to read, people won't use it,
> or will use it less. In Python tuples are easy to read and write, so
> people use them everywhere and they are really useful (especially for
> FP-style programming, where is more common to return multple values).

That is a good point. I won't say I'm convinced for the tuple case <g>, but it is a good argument.
July 24, 2009
On Thu, Jul 23, 2009 at 5:56 PM, Michiel Helvensteijn<m.helvensteijn.remove@gmail.com> wrote:
> Eldar Insafutdinov wrote:
>
>> from your post:
>>
>> property int length {
>>     get() { return this.len; }
>>     set(newLen) { this.len = newLen; }
>>     void opIncrement() { this.len++; }
>> }
>>
>> I don't think that's flexible to overload every operator to get the best performance. As Walter likes to say the best way should be the most obvious. Besides we forgot that D2 allows to return references, which eliminates the issue.
>
> If your property really just hides a private member variable, it was probably for encapsulation purposes or because you want redundant actions to be taken for every access. If you return a reference, you give unlimited and unrestricted access to that variable with only one call, and you might as well not have used a property at all.
>
> If your property is derived -- that is, if it doesn't directly mirror a variable --, there is no reference to return.
>
> Besides, in D, you can probably use mixins to copy the entire interface of a type into the property without code duplication.

You're suggesting adding something like 25 operator overloads to every property.  Can you say "code bloat"?

Why not just use the following solution, which has been proposed God-knows-how-many-times and already has precedence in other languages (like C#)?

obj.prop op= value;

Simply becomes:

obj.prop.set(obj.prop.get op value);
July 24, 2009
On Thu, Jul 23, 2009 at 3:41 PM, Adam D. Ruppe<destructionator@gmail.com> wrote:
> On Fri, Jul 24, 2009 at 12:03:37AM +0200, Michiel Helvensteijn wrote:
>> Would you look at this page and give me your opinion?
>
> Looking at it quickly, the big difference seems to be you leave the tuple word off, and use them in more places.
>
> Is
>
> Tuple!(int, bool) A = tuple(a, b)
>
> really that much worse than:
>
> (int, bool) A = (a, b)
>
> ?

Uh, yes.  Yes it is.
But I have to say I'm always amazed what clutter some people are
apparently willing to put up with when I look at just about any page
of MSDN.

--bb
July 24, 2009
On Fri, Jul 24, 2009 at 12:37:42AM +0200, Michiel Helvensteijn wrote:
> Hm. Then I believe you haven't read the whole thing.

Yeah, I just briefly skimmed it. I'll have to go back to it for a full read when I have a little more time.

> Looking like C is not the be-all and end-all of programming languages.

For a language like D, which hopes to hold on to C programmers, it is very important.

We're an obstinate bunch, set in our ways. I remember the first time I
saw D: I dismissed it because the import statement looked "too Javaish" and
didn't come back until a year or two later.

A change like this would just be too weird, and would surely alienate some of the intended audience before they get past the first look.


-- 
Adam D. Ruppe
http://arsdnet.net