Command–query separation principle [re: @mustuse as a function attribute]
H. S. Teoh
hsteoh at qfbox.info
Wed Oct 19 17:00:39 UTC 2022
On Wed, Oct 19, 2022 at 04:31:59PM +0000, mw via Digitalmars-d wrote:
[...]
> class AbstractPaths {
> AbstractPaths remove (int I) {...}
> }
>
>
> class Paths : AbstractPaths {
> @mustuse
> AbstractPaths remove (int I) {...}
> }
>
>
> AbstractPaths absPaths = new Paths();
>
> absPaths.remove(i); // shall @mustuse be enforced here on absPaths?
No, because it is not enforceable. Consider:
class MyPaths : AbstractPaths {
// N.B.: no @mustuse
override AbstractPaths remove (int I) {...}
}
AbstractPaths absPaths = new MyPaths;
absPaths.remove(i); // shall @mustuse be enforced?
That is, given an instance of AbstractPaths, *you cannot tell* whether
@mustuse should be enforced or not. If it's an instance of Paths, then
it must be enforced, but if it's an instance of MyPaths, then it must
not be. Conclusion: we cannot enforce @mustuse in the base class
AbstractPaths.
> Let's step back, and ask why we want to introduce @mustuse in the
> beginning? The purpose of the annotation is to help programmers do
> not discard important return values accidentally as what the OP's
> author has experienced disaster.
>
> Then the answer is very clear: we do want @mustuse be enforced on the
> absPaths.remove(i) call in the above example!
No, the correct answer is that the base class method must also be
attributed with @mustuse. It should be illegal to have a non- at mustuse
base class method overridden by a @mustuse derived class method.
Why? Consider the following scenarios:
1) Base class has @mustuse, derived class does not. Then the base
class's @mustuse can be circumvented by a derived class that omits the
attribute, defeating the purpose of @mustuse in the base class.
2) Base class does not have @mustuse, but derived class does. Then
instances of the derived class, cast to the base class references,
allow bypassing @mustuse in the derived class.
If indeed @mustuse's purpose is to prevent accidentally discarding
important return values, i.e., something marked @mustuse must *never* be
silently dropped, then neither (1) nor (2) is acceptable.
Conclusion: if the base class method has @mustuse, then so must the
derived class method. If the base class method does not have @mustuse,
then the derived class method cannot have it either.
[...]
> And as a consequence, all the AbstractPaths' derived classes'
> remove(i) method now must have this annotation ( injected by the D
> compiler, the programmer does not have to manually add it in all the
> places).
This is not enforceable, since the base class and derived class could be
in two far-flung files, and due to incremental compilation the compiler
cannot enforce @mustuse in an unmarked method in a class that might have
some distant relative in the inheritance tree that has @mustuse.
The only way this can be done is to make it a compile error for a base
class method and a derived class method to have a mismatch in the
@mustuse attribute.
> That is why I say:
>
> if a method is marked as @mustuse anywhere in the inheritance tree,
> that method in all the class levels in the whole inheritance tree
> became @mustuse!
This is correct. But it cannot be implicit; if you attribute a method
@mustuse somewhere in the inheritance tree, then you must also write
@mustuse in every other class in the hierarchy. Otherwise it's not
enforceable.
T
--
Winners never quit, quitters never win. But those who never quit AND never win are idiots.
More information about the Digitalmars-d
mailing list