www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - relax disabled Final!T unary operators

reply Q. Schroll <qs.il.paperinik gmail.com> writes:
In std.experimental.typecons.Final the operators ++ and -- are 
disabled. I suspect, this was done with simple types as int in 
mind, where increment is nothing different from += 1, which is by 
definition an assignment. From the distant standpoint, there is 
no reason to disable them at all. They are modifying the object, 
but calling a modifying method on a Final! struct/class is not 
disabled either. An operation need not be disabled because it is 
equivalent to an assignment.
I agree that this behavior is natural. Final!int should not be 
modifiable at all, that's precisely what immutable int does. This 
leads to the following conclusion:
Making Final an alias to immutable for types without 
indirections. They cast implicitly to mutable. E.g. Being allowed 
to assign the components of Tuple!(int, int), but not the tuple 
itself is ridiculous because that's the same.
My conclusion: Final only makes sense for things that have 
indirections.

The things are handled similarly for overloading opAssign for 
classes. Identity assignment is disallowed to overload, but not 
other forms, because other forms are just modifying operations. 
The reason why any form of assignment should be disallowed is 
simple: It is confusing and we would have to determine which 
assignments to allow. The Final implementation cannot know which 
assignment to a struct behaves like modifying. Allowing 
non-identity class assignment is an option, but I'm against it: 
It's still an assignment by definition.
Mar 23
next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Mar 23, 2017 at 11:30:43PM +0000, Q. Schroll via Digitalmars-d wrote:
 In std.experimental.typecons.Final the operators ++ and -- are
 disabled. I suspect, this was done with simple types as int in mind,
 where increment is nothing different from += 1, which is by definition
 an assignment. From the distant standpoint, there is no reason to
 disable them at all. They are modifying the object, but calling a
 modifying method on a Final!  struct/class is not disabled either. An
 operation need not be disabled because it is equivalent to an
 assignment.
From another point of view, though, a class that overloads `++` in a
const way is pretty screwed up in terms of abusing the meaning of `++`. I'm not sure if we should go out of our way to support that kind of abuse of operator overloading! Not to mention that the compiler automatically translates: Object obj; ++obj; to the equivalent of: { Object tmp = obj; obj.opUnary!"++"(); return tmp; } so even if Final allows you to call a const opUnary!"++", the preincrement case may still be rejected due to the rebinding in the above lowering. Then we'd end up with the asymmetric situation where obj++ is allowed but ++obj is not.
 I agree that this behavior is natural. Final!int should not be
 modifiable at all, that's precisely what immutable int does. This
 leads to the following conclusion:

 Making Final an alias to immutable for types without indirections.
 They cast implicitly to mutable. E.g. Being allowed to assign the
 components of Tuple!(int, int), but not the tuple itself is ridiculous
 because that's the same.
The same is true for static arrays, which are value types, and thus, if the element type is POD, have no indirections. Yet currently, Final!(int[4]) allows you to modify array elements.
 My conclusion: Final only makes sense for things that have
 indirections.
[...] I agree. The grey area, though, is with structs. What should be the correct behaviour of Final!S when S is a struct? Since structs are value types, it could be argued that only reference members of the struct ought to be modifiable, but the struct itself should not be modifiable. Yet the current implementation allows you to modify struct members freely -- you just can't reassign the entire struct. Which, as you say, is kinda ridiculous because that amounts to essentially the same thing, we're just forcing the user to manually assign struct fields individually instead of having the compiler do it for you. And it's kinda pointless to use Final in this case. But it's not clear how to implement prohibiting the modification of struct members, e.g.: struct S { int x; void method() { x++; } } Final!S s; s.x++; // suppose we prohibit this s.method(); // then should we still allow this? I'm tempted to say that Final should only be applied to class and pointer types (because head const, the original charter of Final, doesn't seem to make sense for types that are inherently by-value). But that would exclude structs that wrap around pointers, e.g., RefCounted, which would seem to be a crippling defect, as we'd *want* to support those kinds of "smart pointer" types. T -- Latin's a dead language, as dead as can be; it killed off all the Romans, and now it's killing me! -- Schoolboy
Mar 23
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
Another grey area (or more precisely, problematic area) with Final is
dynamic arrays. To wit:

	Final!(int[]) arr;
	arr.length += 1;	// correctly rejected
	arr.length = 10;	// accepted - WAT?
	arr.ptr++;		// correctly rejected
	arr.ptr = null;		// correctly rejected

Bugzilla: https://issues.dlang.org/show_bug.cgi?id=17272

Why is it that .length can be assigned, but arr.ptr cannot? (Note that
altering .length may alter .ptr as well, even though direct assignment
to .ptr is rejected.)

It's probably an implementation detail of how Final is implemented...
but still, this does raise the question: should Final allow changing
aggregate members? Dynamic arrays being a special kind of aggregate in
that we normally think of their members as the array elements
themselves, but in implementation dynamic arrays are slices, which are
structs consisting of a pointer and a length. If Final is meant to be
head-const, wouldn't the "head" portion be .length and .ptr?  So we
ought to reject any attempt to modify them. But on the other hand, if we
allow modifying Final struct members, then this is again inconsistent.

All in all, it seems that Final, as currently implemented, really only
makes sense for class types. It seems to have glaring holes and
inconsistency problems with other types.  (Just wait till I try it on a
union... that's gonna throw things off, I'm almost certain.)


T

-- 
Those who've learned LaTeX swear by it. Those who are learning LaTeX swear at
it. -- Pete Bleackley
Mar 24
prev sibling next sibling parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Friday, March 24, 2017 11:00:20 H. S. Teoh via Digitalmars-d wrote:
 All in all, it seems that Final, as currently implemented, really only
 makes sense for class types. It seems to have glaring holes and
 inconsistency problems with other types.  (Just wait till I try it on a
 union... that's gonna throw things off, I'm almost certain.)
Well, final in Java (which is what I assume Final is trying to emulate) is basically head-const. So, if you have a pointer or reference, it would be const, and the rest would be mutable, whereas for types that are not essentially pointers end up being fully const. As such, I would have half-expected Final to just work with pointers and class references, but if it did work with other types, then either it would make anything directly in the type read-only, or it would make the whole type const. Even trying to emulate Java's semantics, it does get a bit funny, because all aggregate types in Java are reference types. So, something like a struct containing a pointer just isn't something that Java's final has to worry about, and if we're trying to emulate it, we're forced to come up with our own semantics. However, I think that what's most in the spirit of Java's final would be to make everything directly in the struct read-only - which in the case of dynamic arrays (which are essentially a struct) would mean that you could alter the elemens but not ptr or the length (regardless of whether altering the length would alter ptr). Now, how implementable that is, I don't know (certainly, it sounds like there are currently quite a few problems with what we currently thave), but if we can't do it right, we should probably just restrict Final to classes and pointers. I don't really have much of a horse in the race though, because I've always thought that Java's final was an utter waste of time. It's making exactly the wrong thing const. If we have it, I'd like to see it implemented in a manner which is not buggy and full of holes, because quality matters, but I don't really think that it was worth adding in the first place. - Jonathan M Davis
Mar 24
prev sibling parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Mar 24, 2017 at 11:00:20AM -0700, H. S. Teoh via Digitalmars-d wrote:
 (Just wait till I try it on a union... that's gonna throw things off,
 I'm almost certain.)
[...] And just as I predicted: https://issues.dlang.org/show_bug.cgi?id=17284 In a nutshell: the compiler rejects assigning to pointer fields in a union when in safe code (for obvious reasons), but to work around that, just write Final!U instead, and you can freely assign overlapping pointers in safe code willy-nilly. I think Final should be modified so that it outright rejects PODs and unions, at the minimum, if not structs as well. I think it's a bad idea to have it accept structs and then allow modifying struct members. This would prohibit things like Final!(RefCounted!T), for example; but if anybody wants to use Final on a RefCounted object, they should perhaps use RefCounted!(Final!T) instead. T -- Without outlines, life would be pointless.
Mar 29
parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Wednesday, 29 March 2017 at 17:59:10 UTC, H. S. Teoh wrote:
 On Fri, Mar 24, 2017 at 11:00:20AM -0700, H. S. Teoh via 
 Digitalmars-d wrote:
 (Just wait till I try it on a union... that's gonna throw 
 things off,
 I'm almost certain.)
[...] And just as I predicted: https://issues.dlang.org/show_bug.cgi?id=17284 In a nutshell: the compiler rejects assigning to pointer fields in a union when in safe code (for obvious reasons), but to work around that, just write Final!U instead, and you can freely assign overlapping pointers in safe code willy-nilly. I think Final should be modified so that it outright rejects PODs and unions, at the minimum, if not structs as well. I think it's a bad idea to have it accept structs and then allow modifying struct members. This would prohibit things like Final!(RefCounted!T), for example; but if anybody wants to use Final on a RefCounted object, they should perhaps use RefCounted!(Final!T) instead. T
As I explained in the bug report (https://issues.dlang.org/show_bug.cgi?id=17284) the particular safe-ty issue has nothing to do with Final. That said, I agree that Final is wrongly designed. In fact I argued that it should reject structs even before the PR was merged, but I didn't have much free time time to defend my position back then: https://github.com/dlang/phobos/pull/3862#issuecomment-199164913
Mar 29
parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Wed, Mar 29, 2017 at 08:11:11PM +0000, via Digitalmars-d wrote:
[...]
 As I explained in the bug report
 (https://issues.dlang.org/show_bug.cgi?id=17284) the particular
  safe-ty issue has nothing to do with Final.
You're right. It seems that the problem is caused by the compiler forgetting to check for overlapping pointer fields when inferring safe for template functions. Which isn't specific to Final.
 That said, I agree that Final is wrongly designed. In fact I argued
 that it should reject structs even before the PR was merged, but I
 didn't have much free time time to defend my position back then:
 https://github.com/dlang/phobos/pull/3862#issuecomment-199164913
While arguments could be made for supporting structs in some way, I think a good first step, since after all this *is* std.experimental, is to add a sig constraint to Final to make it reject everything except pointers, classes, and interfaces. Then in the meantime we can debate over whether / how to implement Final for other types. T -- Only boring people get bored. -- JM
Mar 29