第四章——可选类型(神奇值问题)

275 阅读3分钟

每个优秀的程序员都知道使用“神奇”值是不好的。大多数语言都支持枚举类型,这样可以更安全地表现一组同类型的离散值。

Swift通过“关联值”的概念进一步拓展了枚举。下面的枚举值自己还与其他枚举值相关联:

enum Optional<T> {
case None
case Some(T)
}

在某些语言中,这被称为“标签联合(tagged unions)”(或可辨识联合)。它是一种特殊的数据结构,够存储一组“不同但是固定”的类型中某个类型的对象[1],通过标签来表示具体哪一个类型正在被使用。在Swift中,这个标签就是枚举成员(case语句)。

获取管理值的唯一方法是通过switchif case let。和“哨兵值”不同的是,你必须显式地检查并解封那些包装在Optional类型中的值。

Swift版本的find方法叫indexOf(),它返回的是Optional<Index>而不是直接的Index。这是通过协议拓展实现的:

extension CollectionType where Generator.Element: Equatable {
func indexOf(element: Generator.Element) -> Optional<Index> {
for idx in self.indices {
if self[idx] == element{
return .Some(idx)
}
}
// 没找到,返回 .None
return .None
}
}

由于可选类型非常基础,所以Swift提供了很多语法支持,让代码更加整洁。比如Optional<Index>可以写成Index?。因为可选类型实现了NilLiteralConvertible协议,所以你可以直接写nil而不是.None。除此之外,因为非可选类型的值(如idx)如果需要的话可以自动“升级”成可选类型,所以你可以直接写return idx而不必写return .Some(idx)

现在用户就不会错误的使用无效的值了:

var array = ["one", "two", "three"]
let idx = array.indexOf("four")

//触发编译错误,removeIndex方法的参数是Int类型而不是Optional<Int>
array.removeAtIndex(idx)

因此,如果返回值不为None,你必须“解封”可选类型才能得到其中的真正值。

switch array.indexOf("four") {
case .Some(let idx):
array.removeAtIndex(idx)
case .None:
break
}

这里的switch语句用非常普通的方式写了支持可选类型的枚举代码。当枚举成员是.Some时解封了其中的关联类型。这样写非常安全,但不是很适合阅读和书写代码。Swift2.0引入了后缀语法用来匹配可选类型,还引入了nil字面量来匹配枚举成员None

switch array.indexOf("four") {
case let idx? :
array.removeAtIndex(idx)
case nil:
break
}

这种写法还是显得有些笨重,我们来看一看其他能根据实际用例,让可选类型的处理更加简单的写法。

#译者注:

[1]:比如根据上面的代码我们可以写:

var t = T()
var opt = Optional.Some(t)

于是opt存储的枚举成员为Some,它的关联值为t。

枚举类型中还可以有其他的枚举成员并且关联其他类型的值,但枚举类型的变量在同一时间只能存储一个枚举成员及其关联值。这也就是之前所说的“一组不同但是固定的类型中某个类型的对象”。