为什么我需要一个'<' Array类的重载?

时间:2014-06-28 19:50:53

标签: arrays sorting swift

我正在尝试向Array类添加功能。
所以我试图添加一个类似于Ruby的词典的sort() 为此,如果尊重Swift的sort(),我选择名称'ricSort()'。

但编译器表示它不能为'<'找到重载,尽管'sort({$ 0,$ 1}'by 本身工作正常。
为什么呢?

enter image description here

var myArray:Array = [5,4,3,2,1]
myArray.sort({$0 < $1}) <-- [1, 2, 3, 4, 5]
myArray.ricSort() <-- this doesn't work.

2 个答案:

答案 0 :(得分:1)

这是一个接近您所寻找的解决方案,然后进行讨论。

var a:Int[] = [5,4,3,2,1]

extension Array {

    func ricSort(fn: (lhs: T, rhs: T) -> Bool) -> T[] {
        let tempCopy = self.copy()
        tempCopy.sort(fn)
        return tempCopy
    }
}

var b = a.ricSort(<) // [1, 2, 3, 4, 5]

原始代码存在两个问题。第一个是一个相当简单的错误,即Array.sort没有返回任何值(表示为(),在其他语言中称为voidUnit。因此,以return self.sort({$0 < $1})结尾的函数实际上并没有返回任何内容,我认为这与您的意图相反。这就是为什么它需要return tempCopy而不是return self.sort(...)

这个版本与你的版本不同,它使数组的副本变异,然后返回它。您可以轻松地更改它以使其自身变异(如果您检查编辑历史记录,则帖子的第一个版本会执行此操作)。有些人认为sort的行为(改变数组,而不是返回一个新的行为)是不可取的。一些Apple开发人员列表一直在讨论这种行为。见http://blog.human-friendly.com/swift-arrays-the-bugs-the-bad-and-the-ugly-incomplete

另一个问题是编译器没有足够的信息来生成实现ricSort的代码,这就是你得到类型错误的原因。听起来你想知道为什么它在你使用myArray.sort时能够工作,但是当你尝试在Array上的函数内执行相同的代码时却不能。

原因是你告诉编译器为什么myArray包含:

var myArray:Array = [5,4,3,2,1]

这是

的简写
var myArray: Array<Int> = [5,4,3,2,1]

换句话说,编译器推断myArray由Int组成,而Int恰好符合提供Comparable运算符的<协议(参见:https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/Comparable.html#//apple_ref/swift/intf/Comparable)[1])。从文档中,您可以看到<具有以下签名:

@infix func < (lhs: Self, rhs: Self) -> Bool

根据您具有背景的语言,您可能会惊讶于<是根据语言定义的,而不仅仅是内置运算符。但是如果你考虑一下,<只是一个带有两个参数并返回true或false的函数。 @ infix意味着它可以出现在它的两个函数之间,因此您不必编写< 1 2

(“Self”类型在这里表示“无论此协议实现何种类型”,请参阅https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_597中的协议关联类型声明)

将此与Array.sort的签名进行比较:isOrderedBefore: (T, T) -> Bool

这是通用签名。当编译器处理这行代码时,它知道真正的签名是isOrderedBefore: (Int, Int) -> Bool

编译器的工作现在很简单,只需要弄清楚,是否有一个名为<的函数匹配预期的签名,即一个采用Int类型的两个值并返回Bool的函数。显然<与此处的签名匹配,因此编译器允许在此处使用该函数。它有足够的信息来保证<适用于数组中的所有值。这与动态语言形成对比,动态语言无法预料到这一点。您必须实际尝试执行排序,以了解是否可以对类型进行实际排序。一些动态语言(如JavaScript)将尽可能地继续尝试而不会失败,因此诸如0 < "1"之类的表达式会正确评估,而其他表达式(如Python和Ruby)将会抛出异常。 Swift既不会:它会阻止你运行程序,直到你修复代码中的错误。

那么,为什么ricSort没有工作?因为在创建特定类型的实例之前,没有类型信息可供使用。它无法推断ricSort是否正确。

例如,假设代替myArray,我有这个:

enum Color {
    case Red, Orange, Yellow, Green, Blue, Indigo, Violet
}

var myColors = [Color.Red, Color.Blue, Color.Green]
var sortedColors = myColors.ricSort() // Kaboom!

在这种情况下,myColors.ricSort将因类型错误而失败,因为尚未为Color枚举定义<。这可以在动态语言中发生,但绝不会发生在具有复杂类型系统的语言中。

我还可以使用myColors.sort吗?当然。我只需要定义一个函数,它采用两种颜色,然后以某种顺序返回,这对我的域有意义(EM波长?字母顺序?最喜欢的颜色?):

func colorComesBefore(lhs: Color, rhs: Color) -> Bool { ... }

然后,我可以通过:myColors.sort(colorComesBefore)

希望这表明,为了使ricSort起作用,我们需要以这样的方式构造它,即它的定义保证在编译时,它可以被证明是正确的,而不必运行它或编写单元测试。

希望这能解释解决方案。一些对Swift语言的修改建议可能会使其在未来减少痛苦。特别是创建参数化扩展应该有所帮助。

答案 1 :(得分:0)

您收到错误的原因是编译器无法保证存储在Array中的类型可以与<运算符进行比较。

您可以在数组上看到相同的排序闭包,其类型可以使用<进行比较,如Int

var list = [3,1,2]
list.sort {$0 < $1}

但如果您尝试使用无法与<进行比较的类型,则会出现错误:

var URL1 = NSURL()
var URL2 = NSURL()
var list = [URL1, URL2]
list.sort {$0 < $1} // error

特别是你可以在Swift中省略所有语法,我没有理由为此定义一个方法。以下内容有效且按预期工作:

list.sort(<)

您可以这样做,因为<实际上定义了一个函数,该函数需要两个Ints并返回一个Bool,就像sort方法所期望的那样。