How to constrain a generic sequence parameter to a tuple in Swift 3?

Deadly 提交于 2019-12-13 02:38:50

问题


In Swift 2, I was able to write a function which operated on any sequence of, for example, (String, Int). It looked something like this:

func test<T: SequenceType where T.Generator.Element == (String, Int)>(input: T) {
    for (k, v) in input {
        print(k, "=", String(v))
    }
}

Using a tuple as the constrained type was particularly useful so that it could accept dictionaries, such as [String:Int], as their sequence type consisted of tuples.

In Swift 3, I believe a similar function would be:

func test<T: Sequence>(input: T) where T.Iterator.Element == (String, Int) {
    for (k, v) in input {
        print(k, "=", String(v))
    }
}

But attempting to pass in a [String:Int], for example: test(input: ["a": 1, "b": 2]), causes the error:

Generic parameter 'T' could not be inferred

As far as I can tell, dictionaries in Swift 3 still use a (Key, Value) tuple as their iterator type, so I think this should work. In fact, if I don't use a single type as the constrained iterator type, such as where T.Iterator.Element == String, I can pass in something such as a [String] and it works correctly.

Is there something I'm missing, or might this be a regression in Swift 3?


回答1:


An interesting example.

Let's check the definition of Dictionary about conforming to Sequence:

public func makeIterator() -> DictionaryIterator<Key, Value>

And then DictionaryIterator:

public mutating func next() -> (key: Key, value: Value)?

So, for Dictionary, T.Iterator.Element seems to be (key: Key, value: Value), not (Key, Value).

If you rewrite your function as:

func test<T: Sequence>(input: T) where T.Iterator.Element == (key: String, value: Int) {
    for (k, v) in input {
        print(k, "=", String(v))
    }
}

this works:

test(input: ["a": 1, "b": 2])

But this does not work:

test(input: [("a", 1),("b",2)]) //->Generic parameter 'T' could not be inferred

I'm not sure this is an intended feature or some sort of regression, or simply a bug.




回答2:


This is known bug (SR-992) which in some cases prevents the compiler from matching tuples of the same type, but different labels.

One possible workaround that would allow you to both pass sequences of (String, Int) or (key: String, value: Int) tuples and a [String : Int] dictionary would be to overload test(input:) with a function that expects a sequence with a (key: String, value: Int) element as an input. You could then use a lazily evaluated map in order to 'erase' the tuple labels and then pass that sequence onto your original implementation of test.

// Overload to deal with [String : Int] inputs – workaround for a bug.
func test<T: Sequence>(input: T) where T.Iterator.Element == (key: String, value: Int) {
    // 'Erase' the tuple labels with a lazily evaluated map(_:)
    // and pass the sequence onto the original implementation of test(input:)
    test(input: AnySequence(input.lazy.map{($0.key, $0.value)}))
}

func test<T: Sequence>(input: T) where T.Iterator.Element == (String, Int) {
    for (k, v) in input {
        print(k, "=", v)
    }
}

let d = ["foo" : 5]
let a = [("key", 3)]

test(input: d) // prints: foo = 5
test(input: a) // prints: key = 3

Perhaps not the most ideal solution – but as far as I can see there's no other easy workaround to this problem. Would certainly be interested if anyone has any better ideas.




回答3:


It isn't for swift 3. However, it might help.

Swift 4

Parameterised extensions are not allowed yet. So, we need to use function.

extension Result {
    func zip<A, B, C>(with other: Result<C, Failure>) -> Result<(A, B, C), Failure> where Success == (A, B) {
        return flatMap { (a, b) in
            Result<(A, B, C), Failure>.init(catching: {
                let c = try other.get()
                return (a, b, c)
            })
        }
    }
}

Where Result is

public enum Result<Success, Failure: Error> {
    /// A success, storing a `Success` value.
    case success(Success)

    /// A failure, storing a `Failure` value.
    case failure(Failure)
}


来源:https://stackoverflow.com/questions/39529357/how-to-constrain-a-generic-sequence-parameter-to-a-tuple-in-swift-3

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