At the risk of indulging in and exemplar of bikeshedding:
How about changing style so that !condition
becomes ! condition
. This would increase the visibility of the !
At the risk of indulging in and exemplar of bikeshedding:
How about changing style so that !condition
becomes ! condition
. This would increase the visibility of the !
Agreed on both aspects: requiring whitespace around the !
sigil, and partitioning long method chains into line-separated semantically-meaningful subchains. The above example was clearly just that, a demonstration of how easy it is to lose a juxtaposed leading !
sigil in a long r-value expression.
If it wasn't oddly close to macro syntax, it'd be nice to have infix-like !
operator:
if long.chain.!is_empty()
Parsing of an infix-like !
operator shouldn't be ambiguous. The relevant syntax alternatives seem to be
ExprKind =
| Unary:{ op:UnaryOp expr:Expr }
| Field:{ base:Expr "." field:FieldName }
| MethodCall:{ receiver:Expr "." method:PathSegment "(" args:Expr* % "," ","? ")" }
| β¦
This change would require accepting (a perhaps limited subset of) op:UnaryOp
before both field:FieldName
and method:PathSegment
. Note that for MethodCall
there may still be wide separation between the prefix UnaryOp
and the end of the expression where the UnaryOp
is finally applied. To me that implies that the problem of correlating the operator with it's action is caused by the prefix position of the UnaryOp
; a postfix UnaryOp
would solve the problem by placing the operator in the visual location that corresponds to when it is actually applied.
In summary, I don't think an infix-like !
operator would help; the real need is for postfix unary operators. Postfix macros (e.g., .not!()
) would also be a reasonable way to address this problem of having an action operator visually separated from its point of application.
I'd want to paint that bikeshed is_non_null()
though, matching the NonNull
type.
What would it take to have the compiler automatically infer "is_not_foo" or "not_foo" or "not_is_foo" for all methods that are of the form "is_foo" or even "foo" that return a boolean value? In other words, if a trait or impl is defined that is of the form "is_foo ( ... ) -> bool" then, if the compiler encounters a function or method call like "is_not_foo( ... ) -> bool" it automagically treats it as "!is_foo( ...)" or it automagically auto-monomorphizes "is_not_foo( ... ) -> bool { !foo( .. ) }" (whichever seems like the best strategy from a soundness perspective).
Still, I really like the, bar.is_foo( ... ).not()
that is already supported by the std lib, but, of all the proposals, it seems like inferring a logical negation for all methods/functions that return boolean would be the most ergonomic thing, so you'd have:
fn foo ( ... ) -> bool
implies fn not_foo( ... ) -> bool
exists if not already defined
fn not_foo ( ... ) -> bool
implies fn is_foo( ... ) -> bool
fn is_foo ( ... ) -> bool
implies fn not_foo( ... ) -> bool
fn has_foo ( ... ) -> bool
implies fn not_has_foo( ... ) ->
(similar, for opposite)
etc...
In other words, there is a defined mapping of boolean function name patterns to their logical negation and declaring/defining a function/method of one of those patterns causes the compiler to automatically generate the logical negation according to the proscribed pattern (unless opted-out at the definition site with an attribute).
It seems like this is something that could automatically be done for the next language edition in a backwards compatible fashion. For the current edition it cold be opt-in at the definition site.
In the current edition, if not opted-in, it would not automatically generate the logical negation. On the next edition, if not opted-out, it would automatically generate the negation (unless explicitly opted-out).
Why are you saying that !v.is_empty()
has double negation? Are you considering "empty" to be a negation? If so, then isn't is_nonempty
also a double-negation?
semi-serious suggestion:
if v.pybool() {
....
}
pybool
could be implemented for lots of types (Option
, Vector
, HashMap
, Result
, ...) and it would behave like python's "truthiness" (__bool__
in python3).
I wonder if it would be possible to make pybool
support v as bool
edit: I like is_truthy()
and is_falsey
better though. Someone should make a crate called truthy
and do this. Once everyone is extremely truthy we can make rust more truthy via RFC.
I just had my old C++ PTSD triggered, caused by a colleague's if (!p->is_not_unused()) ...
@_@
While I'm not terribly bothered by is_empty()
, in general I find it easier to correlate truthiness with "has elements". Python for the most part does a good job in that regard.
Not a realistic suggestion at this point in time, but using !!
for negation might have been easier to read, and be symmetric with the other boolean operators using double sigils.
Often I see C programmers (especially with embedded) create Macros for most, if not all, of the Logical/Bit-Wise operations so that they stand out more. This seems to indicate that the use of things like "!" to mean "not" was misguided always and should've never have happened in the C-like languages. Pascal and family got this right. C and family got it wrong. Unfortunately, most modern languages have settled on mostly C-like conventions on this for no reason other than inertial. Perhaps what is needed is a new set of keywords for operators and a lint that recommends to use the keyword instead of operator in most cases.
Clippy is getting this improved!
All of !vec.is_empty()
, vec.is_not_empty()
, and vec.is_nonempty()
have exactly the same message "contains elements because it's not without elements" which has double negation "not without", therefore all of them in this sense are equivalently hard to understand.
Instead a more concise way to say "contains elements" could be vec.collected()
which additionally has a lot of meaning alongside with iter.collect()
.
However, there's a drawback that vec.collected()
is very similar to iter.collect()
, so because of that it may be better to have vec.any_collected()
or something like that.
let words: Vec<_> = text.words().collect();
if words.collected() {
...
}
vs
let words: Vec<_> = text.words().collect();
if words.any_collected() {
...
}
I get that you see "has elements" as positive and "has no elements" as negative.
I don't think the English lines up with your intuition here. "Empty" is a singular concept, explained as "has nothing", but the single words for the concepts are "empty" and a "nonempty".
By this, "is not empty" is a single negation, because it parses as (not (the state of having no contents)), not (not (invert (the state of having contents)). That would instead be "is not nonempty".
If I had to state "has elements" as a positive in today's Rust, I'd do .iter().any(|_| true)
.
I considered "empty" to be a negation in writing that, but I can also see it as just a singular state, as I do with nonempty.
IMHO the reason something like has_elements()
is preferable over is_empty()
is not so much because of a notion of negation, but because it's more commonly useful:
if thing.has_elements() { thing.do_something_with_elements(); }
while thing.has_elements() { thing.pop_element().do_something(); }
Python's notion of truthiness makes these particularly elegant, IMHO:
while customers:
yield customers.pop().get_name() or "Derek"
Would it be possible to move fn len(&self) -> usize
into a trait and then provide is_empty
and has_elements
has provided methods?
Note that the second and third examples here are un-idiomatic in Rust, where they would be
while let Some(top) = thing.pop() { top.do_something(); }
and
while let Some(top) = thing.pop() { yield top.get_name().unwrap_or("Derek"); }
This seems like it moves towards having one more thing you need to memorize in order to write Rust code, which I donβt think is a good thing.
If the clippy lint is the main problem because people copy the first quoted thing instead of the actual suggestion, maybe the order should be swapped.
Otherwise it seems like this would be easy to experiment with via some traits in a third-party crate on the standard types.
I don't have no problems with not unusing operators not unlike not !is_not_unused()
.
Or not?