[英]In Swift, how can I implement a generic system that processes a type by registering processors for different key paths?
I want to implement a system that allows me to generically process a type by registering processors for different key paths.我想实现一个系统,允许我通过为不同的键路径注册处理器来一般地处理一个类型。
One trait of the system should be composition
, so every processor should extend one common generic protocol Processor
.系统的一个特征应该是
composition
,因此每个处理器都应该扩展一个通用的通用协议Processor
。
Example of usage:使用示例:
struct Language {
var name = "Swift"
var version = 5.3
}
var processor = TypeProcessor<Language>()
processor.add(procesor: VersionProcessor(), keypath: \.version)
processor.add(procesor: NameProcessor(), keypath: \.name)
var input = Language()
processor.process(value: input)
// Languge version: 5.3
// Languge name: Swift
Instead of trying to erase property types, your can use inheritance of "processors", where subclasses are generic over root object and property types and have property key path.您可以使用 inheritance 的“处理器”,而不是尝试擦除属性类型,其中子类在根 object 和属性类型上是通用的,并且具有属性键路径。
protocol Processor {
associatedtype T
func process(value: T)
}
class AnyProcessor<Value>: Processor {
func process(value: Value) {
}
}
final class KeyPathProcessor<Root, P: Processor>: AnyProcessor<Root> {
typealias Value = P.T
let keyPath: KeyPath<Root, Value>
let processor: P
init(keyPath: KeyPath<Root, Value>, processor: P) {
self.keyPath = keyPath
self.processor = processor
}
override func process(value: Root) {
processor.process(value: value[keyPath: keyPath])
}
}
struct ObjectProcessor<T>: Processor {
var processors = [AnyKeyPath: AnyProcessor<T>]()
mutating func add<P: Processor, V>(processor: P, keypath: KeyPath<T, V>) where P.T == V {
self.processors[keypath] = KeyPathProcessor(keyPath: keypath, processor: processor)
}
func process(value: T) {
for (_, processor) in processors {
processor.process(value: value)
}
}
}
I've created a playground showing how this can be solved with function composition and then using what we've learned there to recreate your example.我创建了一个游乐场,展示了如何使用 function 组合解决这个问题,然后使用我们在那里学到的知识重新创建您的示例。
Function composition allows you to create new functions by chaining together existing ones, as long as the types match up. Function 组合允许您通过将现有函数链接在一起来创建新函数,只要类型匹配即可。
precedencegroup CompositionPrecedence {
associativity: left
}
infix operator >>>: CompositionPrecedence
func >>> <T, U, V>(lhs: @escaping (T) -> U, rhs: @escaping (U) -> V) -> (T) -> V {
return { rhs(lhs($0)) }
}
The Processor
can be translated to a function that takes an object O
, transforms it in some way, and returns a new object. Creating the function could be done like this: Processor
可以转换为 function,它接受 object O
,以某种方式对其进行转换,并返回一个新的 object。创建 function 可以像这样完成:
func process<O, K>(keyPath: WritableKeyPath<O, K>, _ f: @escaping (K) -> K) -> (O) -> O {
return { object in
var writable = object
writable[keyPath: keyPath] = f(object[keyPath: keyPath])
return writable
}
}
let reverseName = process(keyPath: \Person.name, reverse)
let halfAge = process(keyPath: \Person.age, half)
Now we can compose those two functions.现在我们可以组合这两个函数了。 The resulting functions still keeps the signature `(Person) -> Person).
结果函数仍然保持签名`(Person) -> Person)。 We can compose as many functions as we like, creating a processing pipeline.
我们可以组合任意多的函数,创建一个处理管道。
let benjaminButton = reverseName >>> halfAge
let youngBradPitt = benjaminButton(bradPitt)
Moving on to recreating your example.继续重新创建您的示例。 As this answer mentions, the type is generic over the root object. This is just like in the function composition example, and it allows us to group all the processors in an array for example.
正如这个答案提到的,该类型在根 object 上是通用的。这就像在 function 组合示例中一样,它允许我们将所有处理器分组到一个数组中。
protocol Processor {
associatedtype T
func process(object: T) -> T
}
When erasing an object, it's important to keep a reference to the original, so that we can use it to implement the required functionality.在擦除 object 时,重要的是要保留对原始数据的引用,以便我们可以使用它来实现所需的功能。 In this case, we're keeping a reference to its
process(:)
method.在这种情况下,我们保留对其
process(:)
方法的引用。
extension Processor {
func erased()-> AnyProcessor<T> {
AnyProcessor(base: self)
}
}
struct AnyProcessor<T>: Processor {
private var _process: (T) -> T
init<Base: Processor>(base: Base) where Base.T == T {
_process = base.process
}
func process(object: T) -> T {
_process(object)
}
}
Here's two types implementing the Processor
protocol.这是实现
Processor
协议的两种类型。 Notice the first one has two placeholder types.注意第一个有两种占位符类型。 The second placeholder will get erased.
第二个占位符将被删除。
struct AgeMultiplier<T, K: Numeric>: Processor {
let multiplier: K
let keyPath: WritableKeyPath<T, K>
private func f(_ value: K) -> K {
value * multiplier
}
func process(object: T) -> T {
var writable = object
writable[keyPath: keyPath] = f(object[keyPath: keyPath])
return writable
}
}
struct NameUppercaser<T>: Processor {
let keyPath: WritableKeyPath<T, String>
private func f(_ value: String) -> String {
value.uppercased()
}
func process(object: T) -> T {
var writable = object
writable[keyPath: keyPath] = f(object[keyPath: keyPath])
return writable
}
}
Finally, the ObjectProcessor
that uses object composition.最后,使用object组成的
ObjectProcessor
。 Notice the array holds object of the same type.请注意,该数组包含相同类型的 object。 An instance of this struct will only be able to process
Person
s for example.例如,此结构的实例将只能处理
Person
。 What each child processor does is hidden away in the implementation and the fact it may run on different kinds of data does not affect the ObjectProcessor
.每个子处理器所做的事情都隐藏在实现中,并且它可能在不同类型的数据上运行这一事实不会影响
ObjectProcessor
。
struct ObjectProcessor<T>: Processor {
private var processers = [AnyProcessor<T>]()
mutating func add(processor: AnyProcessor<T>) {
processers.append(processor)
}
func process(object: T) -> T {
var object = object
for processor in processers {
object = processor.process(object: object)
}
return object
}
}
And here it is in action.它正在发挥作用。 Notice I add two processors for the same key.
请注意,我为同一个密钥添加了两个处理器。
var holyGrail = ObjectProcessor<Person>()
holyGrail.add(processor: NameUppercaser(keyPath: \Person.name).erased())
holyGrail.add(processor: AgeMultiplier(multiplier: 2, keyPath: \Person.age).erased())
holyGrail.add(processor: AgeMultiplier(multiplier: 3, keyPath: \Person.age).erased())
let bradPitt = Person(name: "Brad Pitt", age: 57)
let immortalBradPitt = holyGrail.process(object: bradPitt)
Thank you for your answers.谢谢您的回答。 I've used your inputs and come up with the following solution:
我已经使用了您的输入并提出了以下解决方案:
protocol Processor {
associatedtype T
func process(value: T)
}
extension Processor {
func erase()-> AnyProcessor<T> {
return AnyProcessor<T>(self)
}
}
struct AnyProcessor<T>: Processor {
let process: (T) -> Void
init<P: Processor>(_ processor: P) where P.T == T {
self.process = processor.process
}
func process(value: T) {
self.process(value)
}
}
struct KeyPathProcessor<T, V>: Processor {
private var keyPath: KeyPath<T,V>
private var processor: AnyProcessor<V>
init<P: Processor>(_ processor: P, for keyPath: KeyPath<T, V>) where P.T == V {
self.processor = processor.erase()
self.keyPath = keyPath
}
func process (value: T) {
let input = value[keyPath: keyPath]
processor.process(value: input)
}
}
struct VersionProcessor: Processor {
func process(value: Double) {
print("Languge version: \(value)")
}
}
struct NameProcessor: Processor {
func process(value: String) {
print("Languge name: \(value)")
}
}
struct TypeProcessor<T>: Processor {
var processors = [AnyProcessor<T>]()
mutating func add<P: Processor, V>(procesor: P, keypath: KeyPath<T, V>) where P.T == V {
let p = KeyPathProcessor(procesor, for: keypath).erase()
self.processors.append(p)
}
func process(value: T) {
for processor in processors {
processor.process(value: value)
}
}
}
struct Language {
var name = "Swift"
var version = 5.3
}
var processor = TypeProcessor<Language>()
processor.add(procesor: VersionProcessor(), keypath: \.version)
processor.add(procesor: NameProcessor(), keypath: \.name)
var input = Language()
processor.process(value: input)
// Languge version: 5.3
// Languge name: Swift
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.