简体   繁体   中英

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

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)") 
        }
    }
}

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. ie 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)")
}

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()
}

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
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM