PPPan's 平凡之路

非宁静无以致远

做一个互联网内容的贡献者。


从 Swift 中的 max(_:_:) 看设计哲学

由于 Swift 不再支持宏了,于是 MAX,MIN 等一些列常用宏都被重写为 Swift 函数。我们来看看函数的定义:

@warn_unused_result
public func max<T : Comparable>(x: T, _ y: T) -> T  

warn_unused_result

先说说 @warnunusedresult 注解。 顾名思义,被该注解标记的方法所产生的返回值,如果未被使用,编译器会不开心哦(编译的时候会产生一条警告⚠️)。

这里讲一下方法的设计哲学。一般来说一个理想的带有返回值的方法,除了返回返回值以外,是不会产生任何其他副作用的。比如说不会改变入参的值(这也是 inout存在的原因) ,不会对系统的其他状态有影响(比如说不会改变类的实例变量的值)。这样的好处是减少方法产生的不确定性,同时方法使用的目的变得非常明确。

max 来举例。max 方法被设计用来判断并返回两个参数中更大的那个。容易理解,当返回值没有被使用的时候,这次方法的调用也是没有意义的,因此 @warnunusedresult 的标注是非常合理的。

所以当你在开发 SDK 或是框架,甚至是和同事合作开发业务,同事会用到你的 API 的时候,你可以在需要的地方,用此注解来标记你的方法,以提示使用者注意使用方法产生的返回值。

面向协议

从方法的定义中,我们可以看到,传入的参数只要符合 Comparable 协议,就可以正确得到返回结果。

这意味着我们不仅可以用它来比较数字大小:

let maxInteger = max(10, 20)  

还可以用来比较 String 的大小

let maxString = max("bc", "abc")  

甚至是自定义的对象,只要它实现了 Comparable 协议。从这一点上,我们可以窥见 Swift "面向协议编程" 的一斑。面向协议编程让 max(_:_:) 从具体的类中解放出来,不关心入参是什么类,有什么结构,只关心 所传进来的参数能否被比较,而具体的比较则让入参自己去做,从而让 max(_:_:) 具有普适性,达到程序设计上的低耦合。

这么说可能有点抽象,我们来举个具体的例子。

假设我们有这样的餐馆定义

struct Restaurant {  
    var area: Int // 占地面积
    var michelinRate: Int // 米其林等级
    var averageCoast: Int // 人均消费
    var name: String // 餐厅名称
    var logo: UIImage // 餐厅LOGO
}

如何比较两家餐馆谁大谁小?

按照餐馆面积比,平海路上的日料店表示不服;按照人均消费比,新白鹿表示不服;按照米其林等级比,一众特色小吃店表示不服;按照餐厅名称长度比,产品经理表示不服。

总地来说,"谁更大"其实是一种抽象的概念, max 方法不能也不该知道入参是如何比较的。只有入参自己知道自己该如何比较。因此抽象出 Comparable 协议是非常自然的。

Comparable

我们再来看看 Comparable 的定义

public protocol Comparable : Equatable {  
    /// over instances of `Self`.
    @warn_unused_result
    public func <(lhs: Self, rhs: Self) -> Bool
    @warn_unused_result
    public func <=(lhs: Self, rhs: Self) -> Bool
    @warn_unused_result
    public func >=(lhs: Self, rhs: Self) -> Bool
    @warn_unused_result
    public func >(lhs: Self, rhs: Self) -> Bool
}

Comparable 抽象了数学上严格全序的概念。虽然定义了五个方法(别忘了 Equatable 中还有一个 ==),但只需要实现 ==< ,其他方法只需要使用 Swift 标准库提供的默认实现,就可以根据严格全序定义推导出实现。

数学真是计算机科学的基石啊!