How to make generics in collection type constraint?

我是研究僧i 提交于 2019-12-28 04:33:07

问题


I have been trying to extract non-nil values from the String array. Like below. But, my senior wants it to be able to extract non-nil values from other types too.

I read, generics could help me for handling different types. How can I use generics so that I get to use following like extension to work with other types too?

getNonNil must return the extracted non-nil values of the specific type (i.e. if array is [String?] it must return [String], returns [Int] if [Int?])

Because I have to do further calculations.

What I have tried is below:

import Foundation
// Extended the collection-type so that collectiontype is constrained to having element with optional strings
extension CollectionType where Self.Generator.Element == Optional<String>{
    func getNonNil() -> [String] {
        // filter out all nil elements and forcefully unwrap them using map
        return self.filter({$0 != nil}).map({$0!})
    }
}

// Usage
let x: [String?] = ["Er", "Err", nil, "errr"]

x.getNonNil().forEach { (str) in
    print(str)
}

回答1:


For getNonNil you could simply use

x.flatMap { $0 }
// returns ["Er", "Err", "errr"] which is [String]

For the original question, typically you could introduce a protocol to the Optional type (e.g. via the muukii/OptionalProtocol package):

protocol OptionalProtocol {
    associatedtype Wrapped
    var value: Wrapped? { get }
}

extension Optional: OptionalProtocol {
    public var value: Wrapped? { return self }
}

extension CollectionType where Self.Generator.Element: OptionalProtocol {
    func getNonNil() -> [Self.Generator.Element.Wrapped] {
        ...
    }
}



回答2:


There's no easy way of achieving this through an extension, as you cannot introduce new generic types into extensions (although this is part of the Swift Generics Manifesto – so may well be possibly in a future version of Swift).

As @kennytm says, the simplest solution is just to use flatMap, which filters out nil:

x.flatMap{$0}.forEach { (str) in
    print(str)
}

If however, you still want this as an extension, you could use a protocol workaround in order to allow you to constrain the extension to any optional element type (Swift 3):

protocol _OptionalProtocol {
    associatedtype Wrapped
    func _asOptional() -> Wrapped?
}

extension Optional : _OptionalProtocol {
    func _asOptional() -> Wrapped? {return self}
}

extension Collection where Self.Iterator.Element : _OptionalProtocol {
    func getNonNil() -> [Iterator.Element.Wrapped] {
        return flatMap{$0._asOptional()}
    }
} 

...

let x : [String?] = ["Er", "Err", nil, "errr"]

x.getNonNil().forEach { (str) in
    print(str)
}

(In Swift 3, CollectionType has been renamed to Collection, and Generator is now Iterator)

Although flatMap is almost certainly preferred in this situation, I'm only really adding this for the sake of completion.




回答3:


The easiest approach is using flatMap as kennytm suggested, but if you absolutely want to know how to create such a method using generics, one approach would be to create a global method that takes in the collection as a parameter:

public func getNonNil<T, C: CollectionType where C.Generator.Element == Optional<T>>(collection: C) -> [T] {
    return collection.filter({$0 != nil}).map({$0!})
}

let x: [String?] = ["Er", "Err", nil, "errr"]

print(getNonNil(x)) // returns ["Er", "Err", "errr"]


来源:https://stackoverflow.com/questions/38434125/how-to-make-generics-in-collection-type-constraint

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