New keypath subscript

If I override:

subscript<T>(keyPath keyPath: WritableKeyPath<Self, T>) -> T { ... }

it won't be called when I'm getting/setting fields directly.

Could we have another subscript in addition, say:

subscript<T>(proxy keyPath: WritableKeyPath<Self, T>) -> T { ... }

which if specified would be called when I'm getting / setting fields directly?

struct S {
    var x = 1, y = 2
    
    subscript<T>(proxy keyPath: WritableKeyPath<Self, T>) -> T {
        get {
            // call though default implementation:
            let result = self[keyPath: keyPath]
            print("get \(keyPath) -> \(result)")
            return result
        }
        set {
            print("set \(keyPath) = \(newValue)")
            // call though default implementation:
            self[keyPath: keyPath] = newValue
        }
    }
}

var s = S()
s.x = 42 // set \S.x = 42
print(s.x) // get \S.x -> 42

This new "proxy" (bikeshed name) subscript will fill the gap between "keyPath" subscript and "dynamicMember" subscript.

I guess I could use this approach instead:

class S {
    var x: Int {
        get {
            print("get \(\S.x) -> \(_x)")
            return _x
        }
        set {
            _x = newValue
            print("set \(\S.x) = \(newValue)")
        }
    }
    var _x: Int = 1
    
    var y: Int {
        get {
            print("get \(\S.y) -> \(_y)")
            return _y
        }
        set {
            _y = newValue
            print("set \(\S.y) = \(newValue)")
        }
    }
    var _y: Int = 2
    // and so on and so forth
}

or the equivalent code generated by macro, similar to how new Observation machinery is doing it.

I wonder though if the keypath subscript based approach is superior; IMHO it feels lighter and more elegant.

You could do something like this:

@dynamicMemberLookup public struct S {
  public struct Storage {
    var x: Int
    var y: Int
  }

  private var storage: Storage

  public subscript<T>(dynamicMember member: WritableKeyPath<Storage, T>) {
    get { storage[keyPath: member] }
    set { storage[keyPath: member] = newValue }
  }
}
1 Like