February 08, 2013 Re: DIP23 Counter Proposal | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dan | On Thursday, February 07, 2013 23:15:14 Dan wrote:
> On Thursday, 7 February 2013 at 21:55:12 UTC, Jonathan M Davis
>
> wrote:
> > Except that mixins are generally unacceptable in APIs, because
> > they don't end
> > up in the documentation. That means that this works only if you
> > don't care
> > about documentation. So, it's almost useless. Putting @property
> > on there also
> > looks better but isn't that big a deal. However, the lack of
> > documentation
> > _is_ a big deal.
>
> Again, why would you need to @property for the case of a
> read/write pattern.
> Just make it public:
>
> struct S {
> // Best int ever
> int i;
> },
But if you do that, then code that uses S can do stuff like take the address of i or pass it by ref. That code will then break when you turn it into property functions later. That's unacceptable, meaning that in almost all cases, people just create getters and setters which do almost nothing. It's boilerplate that we shouldn't need.
- Jonathan M Davis
|
February 08, 2013 Re: DIP23 Counter Proposal | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | On Thursday, February 07, 2013 17:34:19 Andrei Alexandrescu wrote: > On 2/7/13 4:55 PM, Jonathan M Davis wrote: > > On Thursday, February 07, 2013 16:12:34 Steven Schveighoffer wrote: > >> On Thu, 07 Feb 2013 15:25:57 -0500, Jonathan M Davis<jmdavisProg@gmx.com> > >> > >> wrote: > >>> struct S > >>> { > >>> > >>> @property int i; > >>> > >>> } > >> > >> struct S > >> { > >> mixin(boilerplateProperty("i")); > >> } > >> > >> I don't see this as a difficult problem to solve. > > > > Except that mixins are generally unacceptable in APIs, because they don't end up in the documentation. That means that this works only if you don't care about documentation. So, it's almost useless. Putting @property on there also looks better but isn't that big a deal. However, the lack of documentation _is_ a big deal. > > Well there's the version(DDoc) ... etc. possibility. Except then you're being forced to throw in a different sort of boilerplate code. version(D_Ddoc) { /// DDoc for prop int prop; } else { mixin(boilerplateProperty!int("prop")); } is enough extra that it's arguably worse than just declaring the property functions. If you could do /// DDoc for prop mixin(boilerplateProperty!int("prop")); then that would be okay (though it would be uglier than just putting @property on it to do the same thing). But we can't do that, not and have it show up in the documentation. - Jonathan M Davis |
February 08, 2013 Re: DIP23 Counter Proposal | ||||
---|---|---|---|---|
| ||||
Posted in reply to deadalnix | On 02/08/2013 05:22 AM, deadalnix wrote: > ... > > You have the following behavior : > - & take the address of what is before. > - foo is call to function foo. > - special case : &foo foo(); foo!() tmpl!foo alias sym=foo; > is the first class function foo. (here expression foo have a new meaning). Again, & behaves like that even for variables. auto x = 5; auto y = &5; // error auto z = &x; // ergo: this 'x' is different assert(x==5); // from this one > - In a comma expression, if the last item is a function, it is > evaluated, unless the comma expression is in an address of context, in > which case the function pointer is returned. > - Same goes for ternary, except that both branches are evaluated in > that context. > > This is exactly how special case (3) turtle down to definition of many > other languages constructs. I think ternary and comma is a comprehensible list of the many other language constructs. > For instance, > > int a; > int foo() { return a } > > static assert(is(typeof(foo) == typeof(a))); // Pass > condition ? foo : a; // OK. > &(condition ? foo : a); // Type error between foo of unexpressible type > (static array of instructions) (This does not make any sense. Instructions are often variable-size.) > and a of type int. The error message is a QOI issue. This is what DMD reports if foo is a double, and a is an int: Error: cast(double)a is not an lvalue Error: cannot implicitly convert expression (condition ? & foo : (__error)) of type double* to double* (Another point: As you may have noticed, the constructs &(a?b:c) and &(a,b) are "special cased" _anyway_.) IMHO this is roughly what should be reported in both the case that foo is a function and that foo is a double, maybe giving away a little more information: error: cannot take address of expression '(condition ? foo : a)' &(condition ? foo : a); ^~~~~~~~~~~~~~~~~~~~~~ I think DIP24 is a valid way to go. The simplest alternative design I'd be fine with would be to remove @property, mandate parens on non-ufcs calls, identify function names with their function pointers/delegates, disallow foo = functionArgument and &foo for foo a function or method, and call it a day. (This would not simplify lvalue/rvalue rules, but reduce the number of cases where they apply.) |
February 08, 2013 Re: DIP23 Counter Proposal | ||||
---|---|---|---|---|
| ||||
Posted in reply to deadalnix | On Thu, 07 Feb 2013 23:22:45 -0500, deadalnix <deadalnix@gmail.com> wrote: > On Thursday, 7 February 2013 at 13:56:17 UTC, Steven Schveighoffer wrote: >> On Wed, 06 Feb 2013 21:53:25 -0500, deadalnix <deadalnix@gmail.com> wrote: >> >>> On Wednesday, 6 February 2013 at 21:30:10 UTC, Timon Gehr wrote: >>>>> &(fun, fun) >>>>> >>>> >>>> Agh, comma strikes again. It should be handled analogous to the ternary expression. i.e. the expression above evaluates fun and then returns the function pointer. The DIP now states this. (the second fun is in address-taken position.) This is in agreement to how lvalue positions propagate into comma expressions. >>>> >>> >>> Adding more special cases are not gonna create a good DIP. >> >> I don't they are so much a special case, as they are a clarification. >> >> The two "exceptions" are simply explaining that because ternary operator and comma operators evaluate to an lvalue, it is equivalent to putting the & on the resulting lvalue. >> > > You have the following behavior : > - & take the address of what is before. > - foo is call to function foo. > - special case : &foo is the first class function foo. (here expression foo have a new meaning). > - In a comma expression, if the last item is a function, it is evaluated, unless the comma expression is in an address of context, in which case the function pointer is returned. I think it's consistent, and no special cases. foo is treated as a fucntion name, until it is evaluated without any adornments, in which case it becomes a function call, or when the address operator is applied to it. In other words: &(foo, foo); => foo(); &(foo); => foo(); &foo; (foo, foo); => foo(); foo; => foo(); foo(); Let's note here that by opting on the side of NOT adding optional parens by default, both cases are possible: &(foo, foo()); and &(foo, foo); Under DIP23 (and the current implementation), both are equivalent, and expressive power is less. > - Same goes for ternary, except that both branches are evaluated in that context. I would see the expression the same way, foo's meaning is not evaluated until the ternary expression is resolved. For example: condition ? foo : foo; => foo; => foo(); &(condition ? foo : foo) => &(foo); => &foo; > This is exactly how special case (3) turtle down to definition of many other languages constructs. For instance, > > int a; > int foo() { return a } > > static assert(is(typeof(foo) == typeof(a))); // Pass I'm unsure whether this should pass or fail. It seems disingenuous to make typeof(foo) equivalent to typeof(foo()), since foo is NOT an integer. If we err on the side of expressiveness, typeof(foo) should be the function type, not an int. > condition ? foo : a; // OK. I would say not ok. you have to do: condition ? foo() : a; > &(condition ? foo : a); // Type error between foo of unexpressible type Same, also not ok. This all seems consistent to me. It is more consistent than either the current dmd implementation, or the proposed DIP23. I can see however, that no matter what, there is going to be confusion. The simple rule is that optional parentheses only apply after all other reductions do not consider the function name as a function. The meaning of foo could be explicitly considered as a function call by including the parentheses. -Steve |
February 08, 2013 Re: DIP23 Counter Proposal | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Friday, 8 February 2013 at 05:49:39 UTC, Jonathan M Davis wrote:
> On Thursday, February 07, 2013 23:15:14 Dan wrote:
>> On Thursday, 7 February 2013 at 21:55:12 UTC, Jonathan M Davis wrote:
>>> Except that mixins are generally unacceptable in APIs, because they don't end up in the documentation. That means that this works only if you don't care about documentation. So, it's almost useless. Putting @property on there also looks better but isn't that big a deal. However, the lack of documentation _is_ a big deal.
>>
>> Again, why would you need to @property for the case of a read/write pattern.
>> Just make it public:
>>
>> struct S {
>> // Best int ever
>> int i;
>> },
>
> But if you do that, then code that uses S can do stuff like take the address of I or pass it by ref. That code will then break when you turn it into property functions later. That's unacceptable, meaning that in almost all cases, people just create getters and setters which do almost nothing. It's boilerplate that we shouldn't need.
I hope I'm not missing the point; But I see a big dilemma about the whole ref/not ref(anymore) issue. Then I begin thinking: If you don't Mark it as part of the public API, why should it matter? Unless it's just a storage for data (which you guarantee to be ref), or publicly noted for DDoc, then making use of a internal implementation is wrong (even if it isn't marked private).
It may be similar to...
struct S {
int i;
auto front() {return i;} ///
enum empty = false; ///
void popFront() {i++;} ///
}
//original
void func(S input) {
writeln(input.i);
input.i++;
}
//turned into referring to a range
void func(R)(R input)
if (isRange!(R)) {
//Initially built/tested with struct S and works
//but forgot to update. Still works in his own code
writeln(input.i);
input.i++;
}
If on the other hand if 'i' was intended part of the API, you can rely on it being ref-able unless there's a major change in design.
struct S {
int i; /// iterator number, part of API
ref auto front() {return i;} ///
...
}
Also if the programmer's code gets broke they can always avoid upgrading until later, and fix their code when they are ready for it. Most fixes will be minor (even if they show up dozens of times).
|
February 08, 2013 Re: DIP23 Counter Proposal | ||||
---|---|---|---|---|
| ||||
Posted in reply to Era Scarecrow | On Friday, February 08, 2013 22:38:35 Era Scarecrow wrote:
> If on the other hand if 'i' was intended part of the API, you can rely on it being ref-able unless there's a major change in design.
>
> struct S {
> int i; /// iterator number, part of API
> ref auto front() {return i;} ///
> ...
> }
The idea is that it's supposed to be part of the public API and that you're supposed to be able to add code to it later which does additional checking or completely changes how its implemented internally. This is trivial to do with functions, because they provide proper encapsulation. You can change the implementation as much as you like without breaking the API. But many getter and setter functions just get and set a variable without doing anything else making them useless boilerplate beyond the fact that they provide encapsulation.
So, what property functions are trying to do is allow you to declare it as a public variable first (avoiding the boilerplate) and then only turn into property functions (with the variable being private) if you need the getter or setter to do something extra or different later. The one hitch in that is the fact that there a few things that you can do with a public variable that you can't do with a property function - things that you _don't_ want anyone doing with the public variable (like taking its address or passing it by ref). So, lacking a way to mark a public variable as a property, choosing to start with a public variable makes it likely that you're going to break code when the variable is converted to a pair property functions later. On the other hand, if you can mark the variable as a property (in which case the compiler either makes it illegal to do anything with that variable that you couldn't do with property functions, or it just lowers the variable to a pair property functions, and the variable isn't actually public anymore), then you can avoid breaking code.
But the idea is to do this in a way that's transparent to users of the struct or class. They shouldn't care how a property is implemented. We want the encapsulation even when it's declared as a public variable (because we're really just looking to avoid boilerplate code).
What I'm describing here is how properties are typically presented in C#, and it's what a lot of us would like to be able to do with D.
- Jonathan M Davis
|
Copyright © 1999-2021 by the D Language Foundation