1

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)") 
        }
    }
}
SirEnder
  • 564
  • 4
  • 14

3 Answers3

2

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)")
}
  • I posted your answer using different names, but please don't change yours. It's also very good that people (me included), can learn that they can make use of Swift's context awareness, and as such call such "already used names" to types and vars and protocols. – SirEnder Mar 06 '20 at 09:39
  • You should also keep your solution using initializers. For my particular case/program I won't be using it though, for it is not very compatible with the rest of my program. – SirEnder Mar 06 '20 at 09:42
  • May I suggest that you add/repeat your protocol declaration right above your initializer declaration. For people might be lead to think that the protocol does not need to be there when using such a solution, but it does. – SirEnder Mar 06 '20 at 10:17
  • Sorry. I have been exploring your answer. And... Why do you need to comply with `Swift.ExpressibleByNilLiteral`? I don't see the need. Is there one? – SirEnder Mar 06 '20 at 10:27
  • Protocol inheritance is the closest offered to restricting a protocol to only `Optional`. Every type that is `ExpressibleByNilLiteral` is also `Swift.ExpressibleByNilLiteral`, because there's only one such type. It also makes it so you don't need to come up with a new name. And considering "optable" isn't even in dictionary.com, you can see the safety in it! –  Mar 06 '20 at 12:10
  • At first, I thought you could be right. But then I bothered to look and.. Actually... "Optable" is a word! Perhaps not the best one. But... – SirEnder Mar 06 '20 at 12:17
1

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
    }
}
SirEnder
  • 564
  • 4
  • 14
0

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()
}
BlondeSwan
  • 772
  • 5
  • 26
  • I don't understand what you are saying. How is this any help? Perhaps you are not telling the whole story. Can you post the whole code? – SirEnder Mar 06 '20 at 00:58
  • This solution worked for what you were asking in that it threw errors where you were expecting them because it only allowed you to pass an optional type into `onlyCallableByAnOptable`. Perhaps I was confused by the explanation of your desires, but it works for throwing errors where you expected them – BlondeSwan Mar 06 '20 at 15:03