Regarding static key path schemas (StoredPropertyIterable
), @Douglas_Gregor's reply above suggests defining APIs on MemoryLayout
:
static var storedProperties
can only provide key paths to static properties. However, note that instance-based key path schemas are necessary to support key paths to array elements and dictionary values:
Array.allKeyPaths: [WritableKeyPath<Self, Element>]
: key paths to elements.Dictionary.allKeyPaths: [WritableKeyPath<Self, Value>]
: key paths to values.
We could exposed both static and instance-based key path schema APIs, which was the original intention of the pitch (StoredPropertyIterable
vs CustomKeyPathIterable
) and was suggested in some replies (like this one from @Joe_Groff). I'd like to focus on what an instance-based key path schema API could look like, since that's the more general API.
Regarding instance-based key path schemas, we could try something like:
/// A type that explicitly defines its own key path schema.
// Note: this is similar to `CustomReflectable`.
// Conforming types include `Array` and `Dictionary`.
protocol CustomKeyPathSchema {
/// A collection of all custom key paths of this value.
var allKeyPaths: [PartialKeyPath<Self>]
}
// Extending `Any` with `var allKeyPaths: [PartialKeyPath<Self>]` is not
// possible in Swift code. Instead, we can create an API that takes `Any`
// as an argument and provides `var allKeyPaths: [PartialKeyPath<Self>]`.
struct KeyPathSchema<T> {
var value: T
init(_ value: T) {
self.value = value
}
var allKeyPaths: [PartialKeyPath<T>] {
// Note: we need a `_CustomKeyPathSchema` implementation detail
// similar to `_KeyPathIterable` to work around PAT limitations.
if let customSchemaValue = value as _CustomKeyPathSchema {
return customSchemaValue. _allKeyPathsTypeErased.compactMap { kp in
kp as? PartialKeyPath<T>
}
}
// Fallback: use runtime metadata to get all key paths to:
// - Structs and classes: stored properties.
// - Enums: associated values of the current enum case.
// - Tuples: elements.
...
}
// Include existing `KeyPathIterable` default implementation utilities:
// https://github.com/apple/swift/blob/tensorflow/stdlib/public/core/KeyPathIterable.swift
/// An array of all custom key paths of this value and any custom key paths
/// nested within each of what this value's key paths refers to.
var recursivelyAllKeyPaths: [PartialKeyPath<T>] { ... }
/// Returns an array of all custom key paths of this value, to the specified
/// type.
func allKeyPaths<T>(to _: T.Type) -> [KeyPath<Self, T>] {
return allKeyPaths.compactMap { $0 as? KeyPath<Self, T> }
}
...
}
// Usage:
struct Wrapper<T> {
var item: T
var array: [T]
}
let x = Wrapper<Float>(item: 0, array: [1, 2, 3])
for kp in KeyPathSchema(x).recursivelyAllWritableKeyPaths(to: Float.self) {
x[keyPath: kp] += 1
}
print(x) // Wrapper<Float>(item: 1, array: [2, 3, 4])
Any thoughts?
I think some "key path view" wrapper abstraction is more natural than adding top-level functions like _forEachField
to the global namespace.