Swift Extensions with generic type paramters

Am I missing something here? I cannot see a reason that this would not compile...


protocol StringValidationRule {
    func validate(string: String) throws -> Bool
    var errorType: StringValidationError { get }
}

extension Array where T: StringValidationRule {
    func validateEach(string: String, completion: (valid: Bool, error: StringValidationError?) -> ()) {
        for rule in self {
            do {
                try rule.validate(string)
                completion(valid: true, error: nil)
            } catch let error as StringValidationError {
                completion(valid: false, error: error)
            } catch {
                fatalError("Unexpected error type")
            }
        }
    }
}

var validationRules = ...

validationRules.validateEach("") { (valid, error) -> () in
  
}


Line 23 throws the following error:

error: cannot invoke 'validateEach' with an argument list of type '(String, (_, _) -> ())'

validationRules.validateEach("") { (valid, error) -> () in

Answered by ObjectHub in 18277022

AFAIK, extensions with where clauses only work with protocols. So, instead of extending Array, I would extend the CollectionType protocol. This way, your logic will work for all implementations of CollectionType, including Array.


The code is below. You can try it out in a Swift 2 playground.


typealias StringValidationError = String
protocol StringValidationRuleType {
  func validate(string: String) throws -> Bool
}
extension CollectionType where Generator.Element: StringValidationRuleType {
  func validateEach(string: String, completion: (valid: Bool, error: StringValidationError?) -> ()) {
    for rule in self {
      do {
        try rule.validate(string)
        completion(valid: true, error: nil)
      } catch let error as StringValidationError {
        completion(valid: false, error: error)
      } catch {
        fatalError("Unexpected error type")
      }
    }
  }
}
struct StringValidationRule: StringValidationRuleType {
  let valid: Bool
  func validate(string: String) throws -> Bool {
    return true
  }
}
var validationRules = [StringValidationRule(valid: true), StringValidationRule(valid: true)]

validationRules.validateEach("") { (valid, error) -> () in { }}


== Matthias

The result depends on how you declared the variable validationRules.

If it is typed as [StringValidationRule], sorry, it does not match the condition of your extension.

In the current version of Swift2, protocol type does not conform to the protocol itself.

(It's an odd restriction and should be changed, I think.)


Change your protocol to a class if you can, and your extension works with the current Swift2.

class StringValidationRule {
    func validate(string: String) throws -> Bool {
        fatalError()
    }
    var errorType: StringValidationError {
        fatalError()
    }
}

Ack, I thought this worked with classes AND protocols, but not structs. I hope this is just an unfinished feature and not a design decision considering the push for protocols and value types...

Accepted Answer

AFAIK, extensions with where clauses only work with protocols. So, instead of extending Array, I would extend the CollectionType protocol. This way, your logic will work for all implementations of CollectionType, including Array.


The code is below. You can try it out in a Swift 2 playground.


typealias StringValidationError = String
protocol StringValidationRuleType {
  func validate(string: String) throws -> Bool
}
extension CollectionType where Generator.Element: StringValidationRuleType {
  func validateEach(string: String, completion: (valid: Bool, error: StringValidationError?) -> ()) {
    for rule in self {
      do {
        try rule.validate(string)
        completion(valid: true, error: nil)
      } catch let error as StringValidationError {
        completion(valid: false, error: error)
      } catch {
        fatalError("Unexpected error type")
      }
    }
  }
}
struct StringValidationRule: StringValidationRuleType {
  let valid: Bool
  func validate(string: String) throws -> Bool {
    return true
  }
}
var validationRules = [StringValidationRule(valid: true), StringValidationRule(valid: true)]

validationRules.validateEach("") { (valid, error) -> () in { }}


== Matthias

Ah yes! That was my oversight. Thank you!

As far as I know, you can write extensions with where clause for generic classes or structs.

extension Array where T: CustomStringConvertible {
    func join(joiner: String) -> String {
        var result = ""
        var isFirst = true
        for elt in self {
            if !isFirst {
                result += joiner
            }
            result += elt.description
            isFirst = false
        }
        return result
    }
}
var arr = [1,2,3]
arr.join(",")
var arrPoint = [CGPointZero, CGPoint(x: 1, y: 2)]
arrPoint.join(",") //error: cannot invoke 'join' with an argument list of type '(String)'

But this is another problem.


What I don't understand is your code doesn't work in the Playground of my Mac.

I may be missing something, but I cannot find it...


I got it, { } was just a placeholder and need to be replaced.

validationRules.validateEach("") { (valid, error) -> () in
    //...
}

In this case validationRules's type is [StringValidationRule] (Array of struct), not [StringValidationRuleType] (Array of protocol), so it works.

Thanks! I actually didn't know this would work as well.

Any idea on what's wrong with this? To me it sounds like a bug.


protocol SomeInt {
    var theInt: Int {get set}
}

extension CollectionType where Generator.Element: SomeInt {
    func indexOf(object:Generator.Element) -> Index? {
        return indexOf({ (obj) -> Bool in
            return obj.theInt == object.theInt
        })
    }
}

class PRR: SomeInt {
    var theInt: Int = 0
    init(withInt value: Int){
        theInt = value
    }
}

class container {
    var items: [SomeInt]!
}
let obj1 = PRR(withInt: 1)
let obj2 = PRR(withInt: 2)

let arr = [obj1, obj2]
print(arr.indexOf(obj1)) //This works

let cont = container()
cont.items = [obj1, obj2]
print(cont.items.indexOf(obj1)) //this doesn't

Unfortunately, today, the protocol type (or "existential" as we compiler weenies call it) doesn't conform to the protocol:


In your code, the let-constant `arr` is of type `[PRR]`, PRR conforms to SomeInt, so your extension works.

Seeing `cont.items`, its type is `[SomeInt]`, SomeInt does not conform to SomeInt, so your extension does not work.


I believe this behavior/semantics should be changed. You may call it a bug and can send a Bug Report.

Thank you. This helped me.

Swift Extensions with generic type paramters
 
 
Q