Unary operator key paths

Current solutions to getting the inverse value of a bool using key path syntax, or inverting a number from positive to negative or vice versa require adding extensions to the relevant type.

See discussion toggled or isFalse property/method on bool for use with KeyPath APIs - Evolution / Discussion - Swift Forums

This is one existing workaround.

extension Bool {
  var not: Bool {
    return !self
  }
}

let yes = true
let no = yes[keyPath: \Bool.not] // false

Here is a naive implementation that could work, but doesn't, without something like this pitch here.

let yes = true
let no = yes[keyPath: \!] // Expected expression path in Swift key path

The boilerplate not extension on Bool should not be necessary for a developer to write, because it is simply using a unary operator. It is useful in use cases such as e.g. array.filter(otherArray.contains) since Swift lacks a complementary reject method where a developer could write array.reject(otherArray.contains). Let's not go the ruby route when unary key paths provide value and don't introduce new methods to the namespace.

Enabling unary operators for key path usage also makes it possible to use another unary operator, -.
This example will not compile.


let one = 1
let two = one[keyPath: \-] //Expected expression path in Swift key path

Again, it requires more boilerplate for developers to write and include in the projects they write. See,

extension SignedNumeric {
  var inverse: Self {
    return -self
  }
}

let one = 1
let two = one[keyPath: \.inverse] // -1

Please provide your feedback.

How do you propose distinguishing prefix and postfix operators?

In the standard library the range-formation operator “...” can be used as both. In that specific case the return types are different, but one can easily imagine an operator where they were the same.

1 Like

How would it solve the problem persons.filter(\.isChild.not) after all? How to use \! here? \.isChild.!? What was proposed in the previous thread is !\.isChild, but then I don't think \! is necessary here. Am I missing something?

I’d prefer to be able to apply unary operators to KeyPath directly when Value can use the operator.

For example, I expected to be able to filter an Array of ToDos for ones awaiting completion by just passing a negated KeyPath to its completed property:

let outstanding = todos.filter(!\.completed)

I was surprised when this didn’t work.

5 Likes

Agreed.

Or, even for binary operators, as long as both operands are key paths:

let asciiDigits = "1️⃣2️⃣٣٤56".filter(\.isASCII && \.isNumber)

// maybe even this:
let nonASCIIDigits = "1️⃣2️⃣٣٤56".filter(!\.isASCII && \.isNumber)

! in Swift - in anywhere but a prefix position - always means "force a value where the compiler is not sure it exists", that is, when force unwrapping or in case of try!. Using ! as the last expression in a chain is confusing, more so for beginners, so please let's avoid it. ! communicates "urgency" and Swift leverages this interpretation: the reason for the prefix ! negation is historical, and not really related to how Swift typically represents concepts with symbols, so please let's not proliferate it.

I think it would be better to automatically create keypath versions of operators when defining them, but it doesn't seem possible right now, with the current KeyPath APIs.

2 Likes

Thank you for your response @ensan-hcl. If I understand your suggestion, \.isChild.! is what I did pitch, yes, although @toph42's suggestion of !\.isChild agrees with me.

While I do not understand your question (perhaps a complete code sample would help), !\.isChild was not in proposed in the previous thread from what I can tell.

However, after your comment @toph42 did write out an example using the syntax you wrote was proposed in the previous thread. Applying unary operators to KeyPath directly when Value can use the operator, to use his precise words, sounds like a great idea to me.

This is an excellent point you raise, so good an argument that it likely would block any such proposal of applying unary oparators to KeyPath directly when Value can use the operator.

One proposal not just to move this pitch forward but to advance the language to smooth out its rough edges would be to deprecate ! for force unwrap and add a throwing .forceUnwrap() to Optional, as well as deprecate try!, in my opinion.

Why repurpose a common unary operator used in diverse programming languages already in the design of a new one is entirely beyond me.

Maybe my misunderstanding, sorry. Since the title is 'Unary operator key paths', I thought you are suggesting key paths for operators as \! . (About example, it should be persons.filter(\.isChild.not) , I totally mistook🙏)

I can just barely understand how the example yes[keyPath: \!] works. What I don't understand is, what is \! after all. For exapmle, \.isChild is actuall \Person.isChild . However, what is the Root of \! ? I want to hear more detailed design of UnaryKeyPath here.

@ensan-hcl, the root here in my original example yes[keyPath: \!] is Bool because yes is set to true. It's a poor example in the first place, that I wrote, because let no = !yes works, in this case. @toph42's example expresses the intent of my pitch much better. That is,

let outstanding = todos.filter(!\.completed)
1 Like
Terms of Service

Privacy Policy

Cookie Policy