Search code examples
genericstype-systemsswift3swift-extensionsxcode8

why Swift type system try to convert a type into wrong expected parameter


Using Xcode 8 beta, swift 3 the second extension can not compiled. I don't understand if this is a swift bug or a known limitation.

extension Array {
    func scanl<T>(initial: T, combine:(Iterator.Element, T) -> T) -> [T] {
        guard let first = self.first else { return [] }
        return [initial] + Array(self.dropFirst()).scanl(initial: combine(first, initial), combine: combine)
    }
}


extension Array {
    func scanl<T>(combine: (Iterator.Element, T) -> T) -> [T] {
        guard let first = self.first else { return [] }
        return Array(self.dropFirst()).scanl(initial:first, combine:combine)// Cannot convert value of type '(Element, T) -> T' To expected argument type '(_, _) -> _'
    }
}

(Element, T) -> T is indeed the type of the function. So i cannot understand why the compiler expect (,) -> __ and what is this type mean beside "i don't care about the type"


Solution

  • This is not a bug or a limitation, it's simply impossible for the compiler to ascertain, at compile-time, that first is of type T in your second extension (as T needn't necessarily be the same as Iterator.Element). In both your closures, the compiler knows that first is of type Iterator.Element, but the compiler cannot know whether this is also type T.

    In your first extension, you only use first as the first argument to the combine closure, which expects just the type Iterator.Element, so all is good.

    In your second extension, however, you attempt to pass first as an argument to a parameter (initial) which expects type T, and the compiler cannot know whether first really is of type T (the same type T as used by the combine closure used to call the two-argument scanl), namely that Iterator.Element of self is of type T. This can easily be redeemed by an attempted type conversion (as?) of first to T in the optional binding clause of this second extension.

    extension Array {
        func scanl<T>(combine: (Iterator.Element, T) -> T) -> [T] {
            guard let first = self.first as? T else { return [] }
            return Array(self.dropFirst()).scanl(initial: first, combine: combine)
        }
    }
    

    The fact that Iterator.Element and T needn't necessarily be of the same type is apparent if you construct an example that scans an array of one type to construct an array of another type, e.g.

    /* scant [Int] array to construct [String] array */
    let foo = [1, 2, 3, 4, 5]
    let bar = foo.scanl(initial: "0") { String($0) + $1 }
    print(bar) // ["0", "10", "210", "3210", "43210"]
    

    If you'd only like your scanl method and its collector to produce same-type arrays (as the one being scanned), then you needn't include a generic T, but could use Iterator.Element type in place of T in your extensions above.