How to create a generic function in Swift that will reject the given parameter unless it is an Optional?

旧巷老猫 提交于 2020-05-17 06:56:06

问题


This question is a follow up to my earlier question: I expected the system to report non protocol conformance, but it does not! Why?

Please read the referred question for you to get a better idea of the constraints at hand.

I created a generic function in Swift that will reject its parameter unless such a parameter is Optional. The function I created fully works and does what I desire.

Meaning, any calls to onlyCallableByAnOptable(...), even inside an if let, will yield error due to non-protocol conformance, exactly as desired.

Errors like: Argument type 'UIColor' does not conform to expected type 'Optable'

My only question is: Is there a simpler solution?

To make it clear: func onlyCallableWithAnOptinalParameter<T>(:T)->T needs to work when called in an if let statement like func test() does.

protocol Optable {
    associatedtype OptableType
    func optionalOptable() -> OptableType?
}

func onlyCallableByAnOptable<T>( _ value: T) -> T.OptableType? where T: Optable {
    return value.optionalOptable()
}


extension Optional: Optable {
    typealias OptableType = Wrapped //: Wrapped is the type of the element, as defined in Optional
    func optionalOptable() -> OptableType? {
        return self
    }
}


class TestOptable {
    static func test()
    {
        let c = UIColor.blue
        let s = "hi"
        let i = Int(10)
        let oi: Int? = 10

        if let v = onlyCallableByAnOptable(c) {  // ERROR, as was desired.
            print("color \(v)") 
        }
        if let v = onlyCallableByAnOptable(s) {  // ERROR, as was desired.
            print("string \(v)") 
        }
        if let v = onlyCallableByAnOptable(i) {  // ERROR, as was desired.
            print("integer \(v)") 
        }
        if let v = onlyCallableByAnOptable(oi) {  // OK, as expected.
            print("optional integer \(v)") 
        }
    }
}

回答1:


You might want to give this protocol a better name, but I don't foresee any problems with it as-is, unless you're making your own ExpressibleByNilLiteral types that don't wrap.

protocol ExpressibleByNilLiteral: Swift.ExpressibleByNilLiteral {
  associatedtype Wrapped
}

extension Optional: ExpressibleByNilLiteral { }

func onlyCallableByAnOptional<Optional: ExpressibleByNilLiteral>(_ optional: Optional) -> Optional.Wrapped? {
  optional as? Optional.Wrapped
}

Recommendation: use an initializer. (Downside is the argument label being necessary to disambiguate, but I personally like the explicitness because of how weird this case is. i.e. Swift makes it easy to enforce that something is not optional, but not vice versa.)

extension Optional: ExpressibleByNilLiteral {
  init<Optional: ExpressibleByNilLiteral>(optional: Optional) where Optional.Wrapped == Wrapped {
    self = optional as? Wrapped
  }
}

+

if let v = Optional(optional: i) {  // ERROR, as was desired.
  print("integer \(v)")
}
if let v = Optional(optional: oi) {  // OK, as expected.
  print("optional integer \(v)")
}



回答2:


You need to make the value parameter you pass into onlyCallableByAnOptional optional. Likewise, on the return statement of that function, you will also need to optionally unwrap value so that it can execute the optionalOptable function.

func onlyCallableByAnOptable<T>( _ value: T?) -> T.OptableType? where T: Optable {
    return value?.optionalOptable()
}



回答3:


The following is @Jessy's solution, which is basically a simplification of my solution.

Well done Jessy.

I decided to rewrite it here with different/simpler, hopefully, less "confusing" names for the generic type and the protocol, to make it more readable, let prone to confusion by newbies, and also more similar to the names used in my question.

If anyone happens to know of an even more elegant approach, you are very welcome to post it.

protocol Optable {
    associatedtype Wrapped
}

extension Optional: Optable { }

func onlyCallableByAnOptable<T>(_ value: T) -> T.Wrapped? where T: Optable {
    return value as? T.Wrapped
}

OR, if you happen to prefer Jessy's solution which uses an initializer, here is a renamed version:

protocol Optable {
    associatedtype Wrapped
}

extension Optional: Optable {
    init<T: Optable>(optional o: T) where T.Wrapped == Wrapped {
        self = o as? Wrapped
    }
}


来源:https://stackoverflow.com/questions/60550468/how-to-create-a-generic-function-in-swift-that-will-reject-the-given-parameter-u

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