Add constraints to generic parameters in extension

余生长醉 提交于 2019-11-28 02:52:35

问题


I have this function:

func flatten<Key: Hashable, Value>(dict: Dictionary<Key, Optional<Value>>) -> Dictionary<Key, Value> {
    var result = [Key: Value]()
    for (key, value) in dict {
        guard let value = value else { continue }
        result[key] = value
    }
    return result
}

As you can see, it transforms a [Key: Value?] dictionary into a [Key: Value] one (without the optional).

I wanted to extend the Dictionary class with a new method only for classes which value is an Optional of any type, but I am unable to add constraints to the generic parameters of the dictionary.

This is what I tried:

extension Dictionary where Value: Optional<Any> {
    func flatten() -> [Key: Any] {
        var result = [Key: Any]()
        for (key, value) in self {
            guard let value = value else { continue }
            result[key] = value
        }
        return result
    }
}

But fails with error:

Type 'Value' constrained to non-protocol type 'Optional<Any>'

回答1:


Try this code in the Playground:

// make sure only `Optional` conforms to this protocol
protocol OptionalEquivalent {
  typealias WrappedValueType
  func toOptional() -> WrappedValueType?
}

extension Optional: OptionalEquivalent {
  typealias WrappedValueType = Wrapped

  // just to cast `Optional<Wrapped>` to `Wrapped?`
  func toOptional() -> WrappedValueType? {
    return self
  }
}

extension Dictionary where Value: OptionalEquivalent {
  func flatten() -> Dictionary<Key, Value.WrappedValueType> {
    var result = Dictionary<Key, Value.WrappedValueType>()
    for (key, value) in self {
      guard let value = value.toOptional() else { continue }
      result[key] = value
    }
    return result
  }
}

let a: [String: String?] = ["a": "a", "b": nil, "c": "c", "d": nil]
a.flatten() //["a": "a", "c": "c"]

Because you cannot specify an exact type in the where clause of a protocol extension, one way you may detect exactly the Optional type is to make Optional UNIQUELY conforms to a protocol (say OptionalEquivalent).

In order to get the wrapped value type of the Optional, I defined a typealias WrappedValueType in the custom protocol OptionalEquivalent and then made an extension of Optional, assgin the Wrapped to WrappedValueType, then you can get the type in the flatten method.

Note that the sugarCast method is just to cast the Optional<Wrapped> to Wrapped?(which is exactly the same thing), to enable the usage guard statement.

UPDATE

Thanks to Rob Napier 's comment I have simplified & renamed the sugarCast() method and renamed the protocol to make it more understandable.




回答2:


You can do it in a simpler way. This works with Swift 4:

extension Dictionary {
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> {
         return filter { $1 != nil }.mapValues { $0! }
    }
}

If you don't like the use of higher order functions or need compatibility with previous versions of Swift you can do this as well:

extension Dictionary {
    func flatten<Wrapped>() -> [Key: Wrapped] where Value == Optional<Wrapped> {
        var result: [Key: Wrapped] = [:]
        for (key, value) in self {
            guard let value = value else { continue }
            result[key] = value
        }
        return result
    }
}


来源:https://stackoverflow.com/questions/33436199/add-constraints-to-generic-parameters-in-extension

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!