September 27, 2008
Sergey Gromov wrote:
> In article <gbiqbo$m1e$1@digitalmars.com>, SeeWebsiteForEmail@erdani.org says...
>> Sergey Gromov wrote:
>>> In article <gbgu0h$5sq$1@digitalmars.com>, SeeWebsiteForEmail@erdani.org says...
>>>> Sergey Gromov wrote:
>>>>> I think that property function call feature in general adds an unnecessary ambiguity to the language.  I'd prefer functions to be callable only with regular function call syntax, and properties be usable only with member access syntax.  The same stands for 'unified function call' feature: if you want to inject a method into an 'array of chars' class you do so explicitly, and only the member call syntax is allowed on that method.  Otherwise code tends to become ambiguous and unreadable.
>>>> Experience with other languages has shown that using identical syntax for genuine member access and member function access helps maintainability because it allows a type implementer to switch back and forth between implementing a property as a direct member or as a function, transparently to that type's use.
>>> Sorry I may have been unclear.  I'm not against interchangeability between properties and property accessor methods.  I'm against using property accessors as methods, and against using methods as if they were property accessors.  The current situation actually breaks maintainability because after somebody used .length() you cannot replace it with a public variable anymore.  And you cannot replace
>>>
>>> public void delegate(int) foo;
>>>
>>> with an accessor method because the code 'instance.foo();' will stop working as it used to.
>> I am a bit confused about terminology. Could you please clarify with examples what you mean, also defining all terms (e.g. what does "property accessor" mean?) Thanks.
> 
> Property accessor is a method which is invoked when you syntactically access a data field.  They are called getters and setters in many languages.  In current D, any function or method with zero or one argument is considered a property accessor:
> 
> class Foo
> {
>   int prop()
>   {
>     return field + 1;
>   }
>   int prop(int value)
>   {
>     return field = value + 3;
>   }
>   private int field;
> }
> 
> void useFoo(Foo f)
> {
>   auto x = f.prop;
>   f.prop = 5;
> 
>   auto y = f.prop();  // also allowed
>   f.prop(8);  // ditto
> }

Ok, so far so good.

> The 'prop' family of methods are property accessors in my terminology, and they can be replaced with an actual data field:
> 
> class Foo
> {
>   int prop;
> }
> 
> void useFoo(Foo f)
> {
>   auto x = f.prop;  // fine!
>   f.prop = 5;  // works
> 
>   auto y = f.prop();  // Error: function expected
>   f.prop(8);  // Error: function expected
> }
> 
> You see, properties and methods are *not* interchangeable in current D.  Therefore your correct thesis:
> 
>>>> Experience with other languages has shown that using identical syntax for genuine member access and member function access helps maintainability because it allows a type implementer to switch back and forth between implementing a property as a direct member or as a function, transparently to that type's use.
> 
> does not apply to D.

It does apply, just only one direction. From the above, it looks like a good guideline is to always use the syntax:

auto x = f.prop;
f.prop = x;

because it is more general. And indeed, consider something as innocuous as the empty member for a range. Some here said I should write range.empty() throughout. But that, aside from wrist and eye pain, precludes infinite ranges from implementing empty as simple as:

struct Generator {
    ...
    enum empty = false;
}

But if that implementation were allowed, that would be a boon for generic code because it can easily detect infinite ranges statically.

> Here's another example:
> 
> class Bar
> {
>   int delegate() meth;
> }
> 
> int useBar(Bar b)
> {
>   return b.meth();
> }
> 
> Can you replace 'meth' with a getter?
> 
> class Bar
> {
>   int delegate() meth()
>   {
>     return {return 5;};
>   }
> 
> }
> 
> int useBar(Bar b)
> {
>   return b.meth();  // Error: cannot implicitly convert expression
>   // (b.meth()) of type int delegate() to int
> }
> 
> No you cannot.  But you could be able to do all those nice things if you had an explicit syntax for getters and setters:
> 
> class Foo
> {
>   property int prop() {...}
>   property int prop(int value) {...}
>   property int prop(int v, char c) {...} // syntax error, 2 arguments
>   property int delegate() meth() {...}
> }

I agree. But there's a simpler solution:

int useBar(Bar crystal)
{
  return (crystal.meth)();
}

Looks a bit uneasy on the eye? I agree. But count the lines you use member delegates in; then count the lines you use non-delegate member access; divide the first by the second; multiply by 100; and... is that worth a language feature?

> void useFoo(Foo f)
> {
>   auto x = f.prop;  // OK
>   f.prop = 5;  // OK
> 
>   auto y = f.prop();  // syntax error, function expected
>   f.prop(8);  // syntax error, function expected
> 
>   int z = f.meth();  // OK, the delegate is called
> }
> 
> That being said, I want the same explicit syntax for property and method injection, which are elements of aspect-oriented programming.  Let's call it inject:
> 
> inject T[] reverse(T)(T[] arr) {...}
> inject property T middle(T)(T[] arr)
> {
>   return arr[$/2];
> }
> auto s = "abcde";
> auto sr = s.reverse();  // "edcba"
> auto sm = s.middle;  // 'c'
> reverse(s);  // undefined identifier
> 
> With this, you receive an additional freedom which comes from removing the ambiguity.  Imagine I want my personal hell:
> 
> class Hell
> {
>   private enum random = 666;
>   private inject property size_t realLen(T)(T[] any)
>   {
>     return random;
>   }
>   void tellTheTruth(string s)
>   {
>     writefln("real length of '%s' is %s", s, s.realLen);
>   }
> }
> 
> Here I've injected a useful property into all arrays but the injection is private to my hell only so I can pretend there's still paradise around.

I can imagine you want your personal hell, but I hardly can understand its usefulness to you or the rest of us :o).


Andrei
September 27, 2008
0ffh wrote:
> Andrei Alexandrescu wrote:
>> Steven Schveighoffer wrote:
>>> You are assuming that the C language decision to require parentheses for all functions was a design mistake.  I would argue that the design was on purpose and correctly served that purpose.  The purpose was to remove ambiguity when faced with understanding code without all the context.
>>
>> I have stated my assumption and its basis. What is the basis of yours?
> 
> Yum. I think the problem is that when C was designed, K&R didn't consider
> the use of classes with properties using getter and setter methods.
> Therefore, it made sense to have the naked function name denote the
> function pointer, and make braces following an identifier the mark of a
> function call. I initially had some trouble accepting the explicit & in D
> to get the function pointer myself.

No, that decision didn't make sense regardless of classes and functions. The problem in C is that the name of a function - the simplest thing to type - is also the least useful. If I remember correctly, initially it actually did not mean anything, so you could not do anything to it. Then a rule was added to make the function name AUTOMATICALLY become a function pointer (I mean implicitly add a "&"!). But not always! (Bonus points: under what circumstances?) Did anyone even know that? And this is C - a simple language!

> I wouldn't go so far as to call that property of C a design mistake in
> light of the actual intensions of K&R. But I definitely would defend
> the choice of a language designer to change that feature in light of
> more modern programming paradigms.

I like C, but there are some things that were simply wrongly designed (definition syntax, malloc, and (lack of) array design also come to mind).


Andrei
September 27, 2008
Bent Rasmussen wrote:
>> I think that property function call feature in general adds an unnecessary ambiguity to the language.  I'd prefer functions to be callable only with regular function call syntax, and properties be usable only with member access syntax.  The same stands for 'unified function call' feature: if you want to inject a method into an 'array of chars' class you do so explicitly, and only the member call syntax is allowed on that method.  Otherwise code tends to become ambiguous and unreadable.
> 
> Or abstracted and generic.

Well put!

Andrei
September 27, 2008
Sergey Gromov wrote:
> In article <gbjihf$2803$1@digitalmars.com>, frank@youknow.what.todo.interNETz says...
>> Andrei Alexandrescu wrote:
>>> Steven Schveighoffer wrote:
>>>> You are assuming that the C language decision to require parentheses for all functions was a design mistake.  I would argue that the design was on purpose and correctly served that purpose.  The purpose was to remove ambiguity when faced with understanding code without all the context.
>>> I have stated my assumption and its basis. What is the basis of yours?
>> Yum. I think the problem is that when C was designed, K&R didn't consider
>> the use of classes with properties using getter and setter methods.
>> Therefore, it made sense to have the naked function name denote the
>> function pointer, and make braces following an identifier the mark of a
>> function call. I initially had some trouble accepting the explicit & in D
>> to get the function pointer myself.
>> I wouldn't go so far as to call that property of C a design mistake in
>> light of the actual intensions of K&R. But I definitely would defend
>> the choice of a language designer to change that feature in light of
>> more modern programming paradigms.
> 
> Functions in D are first-class values.  It's awkward that a first-class value cannot be accessed by its identifier.

I agree. But then use of functions for invocation dwarfs use of
functions as first-class values, so I think requiring & for the latter
is a sensible engineering decision.

Besides, it's more efficient to use them as alias parameters, so why not
encourage that too. And aliases do not need a "&" :o).

If you want to discuss language design mistakes, why don't you discuss a real mistake - the infamous "lazy"? Having a storage class change the way a type is used - now that's the perfect example of the tail wagging the dog. Ambiguity, confusion, non-scalability, and sheer nonsense - you can have it all with lazy. Lazy should be either fixed or honorably discharged pronto.


Andrei

September 27, 2008
Sat, 27 Sep 2008 08:56:42 -0500,
Andrei Alexandrescu wrote:
> Sergey Gromov wrote:
> > The 'prop' family of methods are property accessors in my terminology, and they can be replaced with an actual data field:
> > 
> > class Foo
> > {
> >   int prop;
> > }
> > 
> > void useFoo(Foo f)
> > {
> >   auto x = f.prop;  // fine!
> >   f.prop = 5;  // works
> > 
> >   auto y = f.prop();  // Error: function expected
> >   f.prop(8);  // Error: function expected
> > }
> > 
> > You see, properties and methods are *not* interchangeable in current D. Therefore your correct thesis:
> > 
> >>>> Experience with other languages has shown that using identical syntax for genuine member access and member function access helps maintainability because it allows a type implementer to switch back and forth between implementing a property as a direct member or as a function, transparently to that type's use.
> > 
> > does not apply to D.
> 
> It does apply, just only one direction. From the above, it looks like a good guideline is to always use the syntax:
> 
> auto x = f.prop;
> f.prop = x;
> 
> because it is more general.

Now you say that for maintainability to work all users of your library must respect some *guidelines*.  In practice this means that if you have a property method you cannot change it to be a variable because everybody *never* respects guidelines---you know, because you can never please him.

> And indeed, consider something as innocuous as the empty member for a range. Some here said I should write range.empty() throughout. But that, aside from wrist and eye pain, precludes infinite ranges from implementing empty as simple as:
> 
> struct Generator {
>      ...
>      enum empty = false;
> }
> 
> But if that implementation were allowed, that would be a boon for generic code because it can easily detect infinite ranges statically.

Again, my proposal is not about how you write your code.  It's about how others *use* your code and how they limit your freedom in changing your code.  With explicit syntax, if you specified 'empty' being a property and someone put parens after it it would be a syntax error.  Right now you may yell about it's intended to be interchangeable with a constant but someone *will* put parens after it and *their* code will break after you change *your* library.

> > Here's another example:
> > 
> > class Bar
> > {
> >   int delegate() meth;
> > }
> > 
> > int useBar(Bar b)
> > {
> >   return b.meth();
> > }
> > 
> > Can you replace 'meth' with a getter?
> > 
> > class Bar
> > {
> >   int delegate() meth()
> >   {
> >     return {return 5;};
> >   }
> > }
> > 
> > int useBar(Bar b)
> > {
> >   return b.meth();  // Error: cannot implicitly convert expression
> >   // (b.meth()) of type int delegate() to int
> > }
> > 
> > No you cannot.  But you could be able to do all those nice things if you had an explicit syntax for getters and setters:
> > 
> > class Foo
> > {
> >   property int prop() {...}
> >   property int prop(int value) {...}
> >   property int prop(int v, char c) {...} // syntax error, 2 arguments
> >   property int delegate() meth() {...}
> > }
> 
> I agree. But there's a simpler solution:
> 
> int useBar(Bar crystal)
> {
>    return (crystal.meth)();
> }

Yes there are ways to write generic code.  But there are also ways to write non-generic code, and it may be user code which you mignt have no influence upon, and you may be required to keep the user code working no matter what.  Explicit properties grant you such influence, making it impossible for the user to write non-generic code.

> Looks a bit uneasy on the eye? I agree. But count the lines you use member delegates in; then count the lines you use non-delegate member access; divide the first by the second; multiply by 100; and... is that worth a language feature?

Explicit properties solve a much wider range of problems than introduced by naked delegate members.  They just happen to solve this issue, too.

> > That being said, I want the same explicit syntax for property and method injection, which are elements of aspect-oriented programming.  Let's call it inject:
> > 
> > inject T[] reverse(T)(T[] arr) {...}
> > inject property T middle(T)(T[] arr)
> > {
> >   return arr[$/2];
> > }
> > auto s = "abcde";
> > auto sr = s.reverse();  // "edcba"
> > auto sm = s.middle;  // 'c'
> > reverse(s);  // undefined identifier
>
> I can imagine you want your personal hell, but I hardly can understand its usefulness to you or the rest of us :o).

Do you have a clear understanding of how name resolution should work in 'unified call syntax' as it is now?

bool even(char[] a) {...}
class Foo
{
  private OptimizedEvenComputer ec;
  bool even(char[] a) {return ec.even(a);}
  void foo()
  {
    bool even(char[] a) {...}
    char[] l;
    auto x = l.even;
    auto y = even(l);
  }
}

What happens here?  What *should* happen here?  Even now people are confused because the mechanism works against their expectations.  And, most importantly, they disagree in their expectations.  Explicit injection solves this ambiguity once and for all.

And I'm yet to hear *actual* arguments from you.  Till now it was that you didn't like to type parentheses, you didn't like a bit of strictness in the language, and you didn't see any problem in not being able to replace methods with vars.  Oh wait, you did see the problem.  So what's your point then?
September 27, 2008
Sat, 27 Sep 2008 09:13:54 -0500,
Andrei Alexandrescu wrote:
> If you want to discuss language design mistakes, why don't you discuss a real mistake - the infamous "lazy"? Having a storage class change the way a type is used - now that's the perfect example of the tail wagging the dog. Ambiguity, confusion, non-scalability, and sheer nonsense - you can have it all with lazy. Lazy should be either fixed or honorably discharged pronto.

Because I use class and struct fields, I use properties, I use functions, delegates, aliased functions, vararg templates and conditional compilation---but I never had need for lazy yet.
September 27, 2008
Andrei Alexandrescu wrote:
> I agree. But then use of functions for invocation dwarfs use of functions as first-class values, so I think requiring & for the latter is a sensible engineering decision.

the above POV seems skewed to me. D is not C! instead of relying on old
habits we need to form new, better ones.
you basically claim here that "functions as first class values" current
usage is low therefore the syntax should be as it is, while another POV
would be to say: D is relatively a new language that aims to support
more functional programming therefore D should support functions as
first class values more than C and adjust its syntax accordingly.

> 
> Besides, it's more efficient to use them as alias parameters, so why not encourage that too. And aliases do not need a "&" :o).

alias parameters have cons too and the choice what to use should be decided according to the problem at hand.

> 
> If you want to discuss language design mistakes, why don't you discuss a real mistake - the infamous "lazy"? Having a storage class change the way a type is used - now that's the perfect example of the tail wagging the dog. Ambiguity, confusion, non-scalability, and sheer nonsense - you can have it all with lazy. Lazy should be either fixed or honorably discharged pronto.
> 

could you please explain more about what's so bad about lazy in your opinion?

> Andrei
> 
September 27, 2008
Sergey Gromov wrote:

> Do you have a clear understanding of how name resolution should work in 'unified call syntax' as it is now?
> 
> bool even(char[] a) {...}
> class Foo
> {
>   private OptimizedEvenComputer ec;
>   bool even(char[] a) {return ec.even(a);}
>   void foo()
>   {
>     bool even(char[] a) {...}
>     char[] l;
>     auto x = l.even;
>     auto y = even(l);
>   }
> }

I would really hope that the D2 compiler would flag Foo.foo.even as a shadowing violation. I am not a D2 user yet, but I'd hope that Foo.even and Foo.foo.even would not override l.even; it just seems too error prone.

September 27, 2008
Sergey Gromov wrote:
> Sat, 27 Sep 2008 09:13:54 -0500,
> Andrei Alexandrescu wrote:
>> If you want to discuss language design mistakes, why don't you discuss a real mistake - the infamous "lazy"? Having a storage class change the way a type is used - now that's the perfect example of the tail wagging the dog. Ambiguity, confusion, non-scalability, and sheer nonsense - you can have it all with lazy. Lazy should be either fixed or honorably discharged pronto.
> 
> Because I use class and struct fields, I use properties, I use functions, delegates, aliased functions, vararg templates and conditional compilation---but I never had need for lazy yet.

enforce :o)

Andrei
September 27, 2008
Sergey Gromov wrote:
> Sat, 27 Sep 2008 08:56:42 -0500,
> Andrei Alexandrescu wrote:
>> Sergey Gromov wrote:
>>> The 'prop' family of methods are property accessors in my terminology, and they can be replaced with an actual data field:
>>>
>>> class Foo
>>> {
>>>   int prop;
>>> }
>>>
>>> void useFoo(Foo f)
>>> {
>>>   auto x = f.prop;  // fine!
>>>   f.prop = 5;  // works
>>>
>>>   auto y = f.prop();  // Error: function expected
>>>   f.prop(8);  // Error: function expected
>>> }
>>>
>>> You see, properties and methods are *not* interchangeable in current D.  Therefore your correct thesis:
>>>
>>>>>> Experience with other languages has shown that using identical syntax for genuine member access and member function access helps maintainability because it allows a type implementer to switch back and forth between implementing a property as a direct member or as a function, transparently to that type's use.
>>> does not apply to D.
>> It does apply, just only one direction. From the above, it looks like a good guideline is to always use the syntax:
>>
>> auto x = f.prop;
>> f.prop = x;
>>
>> because it is more general.
> 
> Now you say that for maintainability to work all users of your library must respect some *guidelines*.  In practice this means that if you have a property method you cannot change it to be a variable because everybody *never* respects guidelines---you know, because you can never please him.
> 
>> And indeed, consider something as innocuous as the empty member for a range. Some here said I should write range.empty() throughout. But that, aside from wrist and eye pain, precludes infinite ranges from implementing empty as simple as:
>>
>> struct Generator {
>>      ...
>>      enum empty = false;
>> }
>>
>> But if that implementation were allowed, that would be a boon for generic code because it can easily detect infinite ranges statically.
> 
> Again, my proposal is not about how you write your code.  It's about how others *use* your code and how they limit your freedom in changing your code.  With explicit syntax, if you specified 'empty' being a property and someone put parens after it it would be a syntax error.  Right now you may yell about it's intended to be interchangeable with a constant but someone *will* put parens after it and *their* code will break after you change *your* library.
> 
>>> Here's another example:
>>>
>>> class Bar
>>> {
>>>   int delegate() meth;
>>> }
>>>
>>> int useBar(Bar b)
>>> {
>>>   return b.meth();
>>> }
>>>
>>> Can you replace 'meth' with a getter?
>>>
>>> class Bar
>>> {
>>>   int delegate() meth()
>>>   {
>>>     return {return 5;};
>>>   }
>>> }
>>>
>>> int useBar(Bar b)
>>> {
>>>   return b.meth();  // Error: cannot implicitly convert expression
>>>   // (b.meth()) of type int delegate() to int
>>> }
>>>
>>> No you cannot.  But you could be able to do all those nice things if you had an explicit syntax for getters and setters:
>>>
>>> class Foo
>>> {
>>>   property int prop() {...}
>>>   property int prop(int value) {...}
>>>   property int prop(int v, char c) {...} // syntax error, 2 arguments
>>>   property int delegate() meth() {...}
>>> }
>> I agree. But there's a simpler solution:
>>
>> int useBar(Bar crystal)
>> {
>>    return (crystal.meth)();
>> }
> 
> Yes there are ways to write generic code.  But there are also ways to write non-generic code, and it may be user code which you mignt have no influence upon, and you may be required to keep the user code working no matter what.  Explicit properties grant you such influence, making it impossible for the user to write non-generic code.
> 
>> Looks a bit uneasy on the eye? I agree. But count the lines you use member delegates in; then count the lines you use non-delegate member access; divide the first by the second; multiply by 100; and... is that worth a language feature?
> 
> Explicit properties solve a much wider range of problems than introduced by naked delegate members.  They just happen to solve this issue, too.
> 
>>> That being said, I want the same explicit syntax for property and method injection, which are elements of aspect-oriented programming.  Let's call it inject:
>>>
>>> inject T[] reverse(T)(T[] arr) {...}
>>> inject property T middle(T)(T[] arr)
>>> {
>>>   return arr[$/2];
>>> }
>>> auto s = "abcde";
>>> auto sr = s.reverse();  // "edcba"
>>> auto sm = s.middle;  // 'c'
>>> reverse(s);  // undefined identifier
>> I can imagine you want your personal hell, but I hardly can understand its usefulness to you or the rest of us :o).
> 
> Do you have a clear understanding of how name resolution should work in 'unified call syntax' as it is now?
> 
> bool even(char[] a) {...}
> class Foo
> {
>   private OptimizedEvenComputer ec;
>   bool even(char[] a) {return ec.even(a);}
>   void foo()
>   {
>     bool even(char[] a) {...}
>     char[] l;
>     auto x = l.even;
>     auto y = even(l);
>   }
> }
> 
> What happens here?  What *should* happen here?  Even now people are confused because the mechanism works against their expectations.  And, most importantly, they disagree in their expectations.  Explicit injection solves this ambiguity once and for all.
> 
> And I'm yet to hear *actual* arguments from you.  Till now it was that you didn't like to type parentheses, you didn't like a bit of strictness in the language, and you didn't see any problem in not being able to replace methods with vars.  Oh wait, you did see the problem.  So what's your point then?

My point is that I agree with all concerns you are raising but I am not
sure they warrant adding a language feature.

Andrei