通用约束

Generic constraint

我正在尝试在 Swift 中实现通用验证器。验证器只是执行一些验证,即电子邮件验证,并根据值是否有效returns truefalse

protocol Validator {
    associatedtype Value
    
    func validate(_ value: Value) -> Bool
}

我还设法实现了这些验证器。如您所见,associatedValue 很有魅力。

struct EmailValidator: Validator {
    func validate(_ value: String) -> Bool {
        NSPredicate(format: "SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}").evaluate(with: value)
    }
}

struct RequiredValidator: Validator {
    func validate(_ value: String) -> Bool {
        !value.isEmpty
    }
}

但现在我想要一个复合验证器,它不必符合 Validator,但它确实需要接受任何 Validator,其中 associatedValue 是相同。下面我举个例子。

let compoundValidator = CompoundValidator<String>(EmailValidator(), RequiredValidator())

但是,当我尝试实现这个想法时,我 运行 出现了诸如 Cannot specialize non-generic type 'Validator'Cannot convert value of type 'Value' to expected argument type 'Validator.Value'Protocol 'Validator' can only be used as a generic constraint because it has Self or associated type requirements 之类的错误,具体取决于我尝试实现的内容。有没有一种方法可以使用泛型实现这个复合验证器?

struct CompoundValidator<Value> {
    let validators: [Validator<Value>]
    
    init(_ validators: Validator<Value>...) {
        self.validators = validators
    }
    
    func validate(_ value: Value) -> Bool {
        validators.reduce(into: true) { partialResult, validator in
            partialResult = partialResult && validator.validate(value)
        }
    }
}
struct CompoundValidator<Value, T: Validator> where T.Value == Value {
    let validators: [T]

    init(_ validators: T...) {
        self.validators = validators
    }

    func validate(_ value: Value) -> Bool {
        validators.reduce(into: true) { partialResult, validator in
            partialResult = partialResult && validator.validate(value)
        }
    }
}

您可以创建一个 AnyValidator 类型,即具体类型。这样您就可以使用它来指定泛型类型参数。

struct AnyValidator<Value>: Validator {
    private let validateFunc: (Value) -> Bool
    
    init<T: Validator>(_ validator: T) where T.Value == Value {
        validateFunc = validator.validate
    }
    
    func validate(_ value: Value) -> Bool {
        validateFunc(value)
    }
}

extension Validator {
    func eraseToAnyValidator() -> AnyValidator<Value> {
        .init(self)
    }
}

struct CompoundValidator<Value> {
    let validators: [AnyValidator<Value>]
    
    init(_ validators: AnyValidator<Value>...) {
        self.validators = validators
    }
    
    func validate(_ value: Value) -> Bool {
        validators.reduce(into: true) { partialResult, validator in
            partialResult = partialResult && validator.validate(value)
        }
    }
}

用法示例:

CompoundValidator(
    EmailValidator().eraseToAnyValidator(),
    RequiredValidator().eraseToAnyValidator())

您可能希望为少量验证器提供方便的初始化程序:

init<V1, V2>(_ v1: V1, _ v2: V2) where V1: Validator, V2: Validator, V1.Value == Value, V2.Value == Value {
    self.init(v1.eraseToAnyValidator(), v2.eraseToAnyValidator())
}
init<V1, V2, V3>(_ v1: V1, _ v2: V2, _ v3: V3) where V1: Validator, V2: Validator, V3: Validator, V1.Value == Value, V2.Value == Value, V3.Value == Value {
    self.init(v1.eraseToAnyValidator(), v2.eraseToAnyValidator(), v3.eraseToAnyValidator())
}

允许您省略 .eraseToAnyValidator()

这类似于 Combine 的 Publisher type has a merge 运算符,它最多接受 8 个 个参数,只是为了让您可以合并不同类型的 Publisher,而无需说 eraseToAnyPublisher.

让我们从头开始,Validator<Value> 是无效语法,即使 Validator 是具有关联类型的协议。 Swift 还不允许存在性容器(协议引用)声明通用约束,例外是 where 子句。

因此,您需要一个类型橡皮擦才能使用任何类型的泛型:

struct AnyValidator<Value>: Validator {
    private let _validate: (Value) -> Bool
    
    init<V: Validator>(_ validator: V) where V.Value == Value {
        _validate = validator.validate
    }
    
    func validate(_ value: Value) -> Bool {
        _validate(value)
    }
}

extension Validator {
    func eraseToAnyValidator() -> AnyValidator<Value> {
        AnyValidator(self)
    }
}

,您需要更新定义:

struct CompoundValidator<Value> {
    let validators: [AnyValidator<Value>]
    
    init(_ validators: AnyValidator<Value>...) {
        self.validators = validators
    }

但是上面的实现要求所有调用者调用 .eraseToAnyValidator():

let compoundValidator = CompoundValidator(EmailValidator().eraseToAnyValidator(), RequiredValidator().eraseToAnyValidator())

如果你想减轻调用者的负担,那么你需要在被调用方做一些工作:

struct CompoundValidator<Value> {
    let validators: [AnyValidator<Value>]
       
    init<V: Validator>(_ v: V) where V.Value == Value {
        validators = [v.eraseToAnyValidator()]
    }
    
    init<V1: Validator, V2: Validator>(_ v1: V1, _ v2: V2) where V1.Value == Value, V2.Value == Value {
        validators = [v1.eraseToAnyValidator(), v2.eraseToAnyValidator()]
    }
    
    init<V1: Validator, V2: Validator, V3: Validator>(_ v1: V1, _ v2: V2, _ v3: V3) where V1.Value == Value, V2.Value == Value, V3.Value == Value {
        validators = [v1.eraseToAnyValidator(), v2.eraseToAnyValidator(), v3.eraseToAnyValidator()]
    }

,基本上你需要添加尽可能多的初始化程序,因为你想通过代码库传递多少参数。这使您能够以简单的形式调用初始化程序:

let compoundValidator = CompoundValidator(EmailValidator(), RequiredValidator())