[RFC] Key-paths for methods vs. key-path literals with methods

Hi there, I'll try to start the discussion with a question.

Would make sense and be easier to allow key-path literals with methods before allowing key-paths for methods?

I'm asking this question because I think it would be great to have key-path literals that already allow method calls or method references when used in context of functions.

// SE-0249
// example 1
array.map(\.property.method(42).value)

// The key-path literal *could be* desugared to:
array.map { $0[keyPath: \.property].method(42).value }
// or
array.map { $0[keyPath: \.property].method(42)[keyPath: \.value] }

// example 2
array.map(\.property.method)
// The key-path literal *could be* desugared to:
array.map { $0[keyPath: \.property].method }

I think that allowing general key-path for methods would require a lot more work than just the key-path literals for functions. Also keep in mind that in general key-paths with methods would require methods to have hashable parameters, while pure key-path literals with methods for functions do not have this restriction.

In fact I think that this can also bring back the re-evaluation of the old unimplemented proposal by reusing the key-path literal syntax for its solution.

Disclaimer: I cannot implement this myself as I do not have the required knowledge for that task, so it would be hard for me alone to push this topic forward.

2 Likes

I agree that key path literals used in function contexts don't need to be subject to the constraints of KeyPath values.

This shouldn't be that much work, though. It's almost entirely the same as supporting read-only subscripts.

1 Like

In theory nor do subscripts right? So if you had \.value[.key] as (Root) -> Value then the subscript parameter does not need to be hashable. That only is a requirement if we want to construct real key-paths instances.

Right.

1 Like

So if I'm not mistaken I can summarize SE-0042 and SE-0249 to this:

class A {} // not hashable

struct Root {
  var value: Inner

  struct Inner {
    subscript(key: A) -> String { ... }
  }

  func foo(_: Int) {}

  mutating func bar(_: Int) {}
}
// SE-0042
\.foo(_:) as (Root, Int) -> Void // or \.foo
\.bar as (inout Root, Int) -> Void 

// SE-0249 - properties, subscripts and methods
\.value[A()].hasPrefix("swift") as (Root) -> Bool
\.hasPrefix as (String) -> (String) -> Bool

Because of SE-0249 we kinda made the function syntax that we wanted to abandon in SE-0042 relevant again.

// SE-0042 using key-path literals
\.hasPrefix as (String, String) -> Bool

// SE-0249 using key-path literals
\.hasPrefix as (String) -> ((String) -> Bool)