快速的奇怪的泛型行为

时间:2016-02-26 17:34:56

标签: ios swift macos generics

美好的一天!

我目前正在尝试学习swift编程并尝试一些泛型的例子。这是我的示例代码

func findAll<T: Equatable>(arr: [T], _ elem: T) -> [Int] {
    var indexesArr = [Int]()
    var counter = 0

    for i in arr {
        if i == elem {
           indexesArr.append(counter)
        }
        counter++
    }

    return indexesArr
}

findAll([5, 3, 7, 3, 9], 3)
findAll(["a", "b", "c", "b", "d"], 2.0)

假设两个参数都属于同一类型。但是,不幸的是,你可以看到我在我的第二个函数调用中传递了一个字符串数组和一个Double数组,它仍然有效!它返回一个空数组[]。没有运行时或编译错误。

请解释一下为什么它有效并且可能有些变通方法。谢谢!

2 个答案:

答案 0 :(得分:1)

大概你的代码中有import Cocoaimport Foundation。如果你删除它,你会发现你的代码表现得像你期望的那样。

导致这种情况发生的原因是导入Foundation会导致Swift.Array神奇地桥接到NSArray(又名[AnyObject]),并且某些原始值类型会神奇地桥接到Foundation对象类型(例如{ {1}}和NSString)。一旦发生这种情况,使用NSNumber[AnyObject]类型的参数调用您的函数是完全合法的。

我不知道魔术转换的良好的方式,但一种方法是强制你的泛型参数拥有一个原始值类型符合的协议,但原始值神奇地包裹在Cocoa对象中没有。确保这样做的一种方法是创建自己的协议:

AnyObject

我不确定这是最好的方式,但是......欢迎提出替代方案。

顺便说一下,做这样的事情的更惯用/更好的方式是作为类型扩展(协议扩展,如下所示,或public protocol NotAnyObject {} extension Int: NotAnyObject {} extension String: NotAnyObject {} extension Double: NotAnyObject {} func findAll<T: Equatable where T: NotAnyObject>(arr: [T], _ elem: T) -> [Int] { /*...*/ } 上的扩展)。你甚至可以去功能性编程风格:

Array where Element: Equatable

这样称呼:

extension CollectionType where Generator.Element : Equatable {
    public func allIndexesOf(element: Self.Generator.Element) -> [Self.Index] {
        return zip(self.indices, self) // makes sequence of (index, element)
            .filter { $0.1 == element } 
            .map { $0.0 }
    }
}

但仍然存在相同的魔术转换问题:

let threes = [5, 3, 7, 3, 9].allIndexesOf(3)
// returns [1, 3]

因此,您仍然需要仅从不导入Foundation的Swift文件中使用此扩展,或者应用与上述相同的hack的变体,例如:

let notThrees = ["twenty", "forty", "eight"].allIndexesOf[2.0]
// still compiles, returns []

答案 1 :(得分:-1)

有关问题说明,请参阅rickster's answer

请注意,要解决此类问题,您可以随时查看Swift库的执行方式。解决方案是将两个参数都作为泛型并使用协议约束。

E.g。使用SequenceType协议而不是Array

func findAll<S: SequenceType, T: Equatable where S.Generator.Element == T>(arr: S, _ elem: T) -> [Int] {
    var indexesArr = [Int]()
    var counter = 0

    for i in arr {
        if i == elem {
            indexesArr.append(counter)
        }
        counter++
    }

    return indexesArr
}