【译】Swift文意

119 阅读8分钟

原文地址 Swift Literals

1911年,语言学家弗朗兹**·博阿斯观察到爱斯基摩-阿留申语**的使用者使用不同的词来区分落下的雪花和地上的雪。相比之下,说英语的人通常都称两者为“雪”,但雨滴和水坑之间有类似的区别。

随着时间的推移,这种简单的经验观察已经扭曲成一种可怕的陈词滥调,即“爱斯基摩人*[*有50个不同的词来形容雪”——这是不幸的,因为博阿斯最初的观察是经验性的,由此产生的语言相对论的弱主张是没有争议的:语言将语义概念分成不同的词,这些词可能(而且经常)彼此不同。这是历史的偶然事件,还是反映了一种文化的更深层次真相,尚不清楚,有待进一步辩论。

正是在这个框架中,您被邀请考虑Swift中不同类型的文字如何塑造我们对代码的推理方式。

标准文字

文字是源代码中值的表示形式,例如数字或字符串。

Swift提供以下几种文字:

NameDefault Inferred TypeExamplesIntegerInt123, 0b1010, 0o644, 0xFF,Floating-PointDouble3.14, 6.02e23, 0xAp-2StringString"Hello", """ . . . """Extended Grapheme ClusterCharacter"A", "é", "🇺🇸"Unicode ScalarUnicode.Scalar"A", "´", "\u{1F1FA}"BooleanBooltrue, falseNilOptionalnilArrayArray[1, 2, 3]DictionaryDictionary["a": 1, "b": 2]

理解Swift中的文字最重要的一点是,它们指定了一个值,但不是一个确定的类型。

当编译器遇到文字时,它会尝试自动推断类型。它通过查找可以由这种文字初始化的每个类型,并根据任何其他约束将其缩小。

如果无法推断出任何类型,Swift会初始化这种文字的默认类型——整数文字的Int,字符串文字的String,等等。

57 // Integer literal"Hello" // String literal

在零字面值的情况下,类型永远不能自动推断,因此必须声明。

nil // ! cannot infer typenil as String? // Optional<String>.none

对于数组和字典文本,集合的关联类型是根据其内容推断出来的。然而,推断大型或嵌套集合的类型是一项复杂的操作,可能会大大增加编译代码所需的时间。您可以通过在声明中添加显式类型来保持快速。

// Explicit type in the declaration// prevents expensive type inference during compilationlet dictionary: [String: [Int]] = [    "a": [1, 2],    "b": [3, 4],    "c": [5, 6],    …]

游乐场文字

除了上面列出的标准文字之外,游乐场中的代码还有一些额外的文字类型:

NameDefault Inferred TypeExamplesColorNSColor / UIColor#colorLiteral(red: 1, green: 0, blue: 1, alpha: 1)ImageNSImage / UIImage#imageLiteral(resourceName: "icon")FileURL#fileLiteral(resourceName: "articles.json")

在iPad上的Xcode或Swift游乐场中,这些以octothorpe为前缀的文字表达式会被一个交互式控件自动替换,该控件提供所引用的颜色、图像或文件的视觉表示。

// Code#colorLiteral(red: 0.7477839589, green: 0.5598286986, blue: 0.4095913172, alpha: 1)// Rendering🏽

img

此控件还可以轻松选择新值:您将看到一个颜色选择器或文件选择器,而不是输入RGBA值或文件路径。

大多数编程语言都有布尔值、数字和字符串的文字,许多语言有数组、字典和正则表达式的文字。

文字在开发人员的编程思维模型中根深蒂固,以至于我们大多数人都不会积极考虑编译器实际上在做什么。

对这些基本构建块有一个简写,使代码更容易读写。

文字是如何工作的

文字就像文字:它们的意义可以根据周围的环境而改变。

["h", "e", "l", "l", "o"] // Array<String>["h" as Character, "e", "l", "l", "o"] // Array<Character>["h", "e", "l", "l", "o"] as Set<Character>

在上面的例子中,我们看到默认情况下,包含字符串文字的数组文字被初始化为字符串数组。然而,如果我们显式地将第一个数组元素转换为字符,文字将被初始化为字符数组。或者,我们可以将整个表达式转换为Set来初始化一组字符。

这是怎么运作的?

在Swift中,编译器通过查看实现相应文字表达式协议的所有可见类型来决定如何初始化文字。

LiteralProtocolIntegerExpressibleByIntegerLiteralFloating-PointExpressibleByFloatLiteralStringExpressibleByStringLiteralExtended Grapheme ClusterExpressibleByExtendedGraphemeClusterLiteralUnicode ScalarExpressibleByUnicodeScalarLiteralBooleanExpressibleByBooleanLiteralNilExpressibleByNilLiteralArrayExpressibleByArrayLiteralDictionaryExpressibleByDictionaryLiteral

要符合协议,类型必须实现其所需的初始值设定项。例如,Express By整数字典协议需要init(integer Literal:)。

这种方法真正伟大的地方在于,它允许您为自己的自定义类型添加文字初始化。

支持自定义类型的文字初始化

在适当的时候支持文字初始化可以显著改善自定义类型的人体工程学,让它们感觉像是内置的。

例如,如果您想支持**模糊逻辑**,除了标准布尔费用之外,您还可以实现如下模糊类型:

struct Fuzzy: Equatable {    var value: Double    init(_ value: Double) {        precondition(value >= 0.0 && value <= 1.0)        self.value = value    }}

Fuzzy值表示在0到1(包括)的数值范围内介于完全真和完全假之间的真值。也就是说,值1表示完全真,0.8表示大部分真,0.1表示大部分假。

为了更方便地使用标准布尔逻辑,我们可以扩展Fuzzy以采用Express By Boolean Literal协议。

extension Fuzzy: ExpressibleByBooleanLiteral {    init(booleanLiteral value: Bool) {        self.init(value ? 1.0 : 0.0)    }}

实际上,没有多少情况适合使用布尔文字初始化类型。对字符串、整数和浮点文字的支持要普遍得多。

这样做不会改变真假的默认含义。我们不必担心现有的代码中断,仅仅因为我们在代码库中引入了半真半假的概念(“视图确实看起来是动画的*……也许?”).* 真假初始化模糊值的唯一情况是编译器可以推断出模糊的类型:

true is Bool // truetrue is Fuzzy // false(true as Fuzzy) is Fuzzy // true(false as Fuzzy).value // 0.0

因为模糊是用单个Double值初始化的,所以允许用浮点文字初始化值也是合理的。很难想象有什么情况下一个类型支持浮点文字,但不支持整数文字,所以我们也应该这样做(然而,反之亦然;有很多类型可以使用整数而不是浮点数)。

extension Fuzzy: ExpressibleByIntegerLiteral {    init(integerLiteral value: Int) {        self.init(Double(value))    }}extension Fuzzy: ExpressibleByFloatLiteral {    init(floatLiteral value: Double) {        self.init(value)    }}

随着这些协议采用,模糊类型现在看起来和感觉上都像是Swift标准库的真正成员。

let completelyTrue: Fuzzy = truelet mostlyTrue: Fuzzy = 0.8let mostlyFalse: Fuzzy = 0.1

现在唯一要做的就是实现标准逻辑运算符!)

如果您希望优化便利性和开发人员生产力,则应考虑实现适合自定义类型的任何文字协议。

未来发展

文字是语言未来讨论的一个活跃话题。展望Swift 5,有许多当前的提议可能对我们如何编写代码有极好的影响。

原始字符串文字

在撰写本文时,**Swift Evolution提案0200**正在积极审查中。如果被接受,Swift的未来版本将支持“原始”字符串,或忽略转义序列的字符串文字。

从提案中:

我们的设计增加了可自定义的字符串分隔符。您可以用一个或多个#(磅、数字符号、U+0023)字符填充字符串文字 […] 字符串开始时的磅符号数量(在这些例子中,零、一和四)必须与字符串末尾的磅符号数量相匹配。

"This is a Swift string literal"#"This is also a Swift string literal"#####"So is this"####

这个提议是Swift 4**(SE-0165**)中添加的新的多行字符串文字的自然扩展,并且将使处理JSON和XML等数据格式变得更加容易。

除此之外,采用这个建议可以消除在视窗上使用Swift的最大障碍:处理像C:\视窗\所有用户\应用程序数据这样的文件路径。

通过强制初始化

最近的另一个提议,**SE-0213:通过强制的文字**初始化已经在Swift 5中实现。

从提案中:

如果可能的话,应该使用适当的文字协议构造T。

目前,符合文字协议的类型是使用常规初始化器规则进行类型检查的,这意味着对于像UI nt 32(42)这样的表达式,类型检查器将查找一组可用的初始化器选项,并逐个尝试它们,试图推断出最佳解决方案。

在Swift 4.2中,使用最大值初始化UI nt 64会导致编译时溢出,因为编译器首先尝试用文字值初始化Int。

UInt64(0xffff_ffff_ffff_ffff) // overflows in Swift 4.2

从Swift 5开始,这个表达式不仅会成功编译,而且会更快一点。

语言使用者可用的词汇不仅影响他们说什么,也影响他们如何思考。同样,编程语言的各个部分对开发人员的工作方式有着相当大的影响。

Swift划分值语义空间的方式使其不同于那些不区分整数和浮点的语言,或者对字符串、字符和Unicode标量有单独的概念的语言。因此,当我们编写Swift代码时,我们经常在比侵入JavaScript更低的级别考虑数字和字符串,这不是巧合。

同样,与其他脚本语言相比,Swift目前缺乏字符串文字和正则表达式之间的区别,导致正则表达式的使用相对缺乏。

这并不是说拥有或缺乏某些单词会使表达某些想法变得不可能——只是有点模糊。我们可以理解“不可翻译”的单词,比如葡萄牙语的“沙特”、韩语的“汉”或德语的“****韦尔茨默兹”。

我们都是人。我们都理解痛苦。

通过允许任何类型支持文字初始化,Swift邀请我们成为更大对话的一部分。利用这一点,让你自己的代码感觉像标准库的自然扩展。