[Pitch] Chained logical NOT `.!`

Proposed: .! as a chained logical NOT:

Existing negation by prepending ! to negate a long call chain splits the negation from what it negates:

!funcA(argument1, argument2) // This reads like "not some long chain"
.var123
.fdhbjkvdfjhlbvdjbnvj(dmnbvdbf, sjnnfsdvkf)
.contains(something)

I think it is more visually understandable if the negation operator is prepended to the call that it is negating:

funcA(argument1, argument2)
.var123
.fdhbjkvdfjhlbvdjbnvj(dmnbvdbf, sjnnfsdvkf)
.!contains(something) // This reads like "does not contain"

I've nicknamed this the Pinocchio operator, taking inspiration from the Elvis operator (it looks like a profile of Pinocchio's eye & long nose, plus it inverts the truth, like Pinocchio lying).

Addendum: this should also be available for use with KeyPaths, like .map(\.!isEnabled).

FYI, there have been some rejected proposals in this area before, particularly a Bool.toggled property.

I personally think it’s worth bringing up again. I don’t find @xwu’s argument convincing; IMO operators are conveniences for standard property- or method-invocation syntax, and the fact that static func Bool.! has no equivalent property is a frustrating anomaly.

1 Like

Assuming this wasn't an April's fool day joke.

I am sorry, but this sort of thing should not be in any language:-1:

What benefit does it offer?

That can be rewritten as :slight_smile:

let u = funcA(argument1, argument2)
let isContained = u.var123.fdhbjkvdfjhlbvdjbnvj(dmnbvdbf, sjnnfsdvkf)
.contains(something)

// Do something with isContained
...
1 Like

The more general problem is the need to be able to express negation in combination with frameworks and libraries that are built atop suffix syntax such as key paths. While .! might not pass muster as an infix operator, you just can’t express myDynamicBoolProperty[keyPath: \.!.someProperty] in the language today.

3 Likes

I haven't figured out if I like this or not…but unfortunately, .! is already a valid infix operator in Swift, so this would be a breaking change.

4 Likes

And at the risk of stating the obvious, !. already means “force-unwrap and access property”.

Again, speaking personally, I think it would be neat if we could spell something.(!someBool), especially as an implicit keypath expression.

Your rewrite omitted the negation, which is the whole point of this pitch.

Did you have a typo in your rewrite?

1 Like

No, the negation operator may be applied to the expression isContained at the point of use.

Sorry, I should have made it explicit.

if !isContained {
   ...
}

// or
guard !isContained else {
...
}

// or
return !isContained

// or, etc
1 Like

@ibex: Single-use variables are unnecessarily verbose.

I think this can make code really more readable, but in my opinion it’s too far from common syntax known from other languages.

But when debugging, they are extremely useful. :slight_smile:

4 Likes

I’ve defined a Bool extension not as !self for that exact purpose, that way it reads like \.this.example.isValid.not

4 Likes

You could always use == false to shift things to the right-hand side

funcA(argument1, argument2)
.var123
.fdhbjkvdfjhlbvdjbnvj(dmnbvdbf, sjnnfsdvkf)
.contains(something) == false
2 Likes

This is just a specialization of the general missing feature of post-applicatives.

#expect(false&.map(!)) // âś…
&
public struct And<Value> {
  @usableFromInline let value: Value
  @inlinable init(_ value: Value) { self.value = value }
}

postfix operator &
@inlinable public postfix func &<Value>(value: Value) -> And<Value> {
  .init(value)
}

public extension And {
  @inlinable func map<Transformed, Error>(
    _ transform: (Value) throws(Error) -> Transformed
  ) throws(Error) -> Transformed {
    try transform(value)
  }
}

Key paths could be handled by this, but not with typed throws, which crash the compiler because they have been silently abandoned:

public extension KeyPath {
  func map<Transformed, Error>(
    _ transform: @escaping (Value) throws(Error) -> Transformed
  ) -> (Root) throws(Error) -> Transformed {
    { try transform($0[keyPath: self]) }
  }
}

public extension KeyPath where Value == Bool {
  static prefix func !(keypath: KeyPath) -> (Root) -> Value {
    keypath.map(!) //Command SwiftCompile failed with a nonzero exit code
  }
}
3 Likes

It might sound like a "not" joke, but I really like trailing .not. Used it in a couple of project of mine.

6 Likes

On the off chance that this is not an April’s Fool Joke:

While the version with the KeyPath might be interesting (more on that later) the bare version presented in the original comment is just not going achieve its stated goal.

When reading code, operator precedence is baked-in in our collective mind. We know, and every language behave this way, that the unary not is at the beginning of the expression. If the expression is too long or complex, then use a variable, or == false if you really want the negation to be at the end of the expression, as already suggested.

Even though they shouldn’t both compile for the same expression, .! and !. will be so close visually that this will make easy-to-miss errors or at least create confusion. It’s only a matter of time until we will end up with ?.! and !.! in the code, and I feel that closing the gap with APL syntax is a non goal for Swift.

a .! somewhere in the middle or end of a long chain will be very easily missed. I don’t think it will increase the readability. In fact, I think it will do the exact opposite.

Regarding #expect(false&.map(!)) // ✅, I think this points towards something broader in Swift, which is the missing Scope Functions that Kotlin has (Scope functions | Kotlin Documentation), in particular, let and apply. Whatever the name is, (I’m not a big fan of map for non-list containers, but this is a completely separate discussion).

Finally regarding the KeyPath approach: If we start including operators in KeyPath, should we include the Optional ! as well, or any of the other unary operators? I’m not sure this is something we want to encourage.

A simple computed var not or toggled in an extension Bool will enable all the use cases in this thread, with a much better readability (to my eyes) than an operator somewhere in the middle of the property chain.

In conclusion, I don’t feel lile this will add much to the language and workarounds are already available.

5 Likes