Swift 学习笔记(@autoclosure 与 call-by-name)

咋一眼看到 Swift @autoclosure 自动闭包便不由自主的联想到 Scala 的 Call-By-Name 求值策略。既然主要话题是关于 Swift 的,那还是 Swift 的 @autoclosure 先入为主,下面例子中的 greet(name: () -> String) 接收一个闭包作为参数

greet(_:) 的函数调用不断简化,最后简化为 greet{"Swift}。总而言之大括号是甩不了了,有大括号的围绕才叫闭包。

同时我们依据闭包的简化规则倒退发现:给任何一个表达式两边加上大括号 {},就构成了一个闭包,它是无参数的闭包,返回值可有可无。例如 {1+2}, {foo(10)+5}, {print("Hello")}, {array.removeAtIndex(0)} 等等

至此,我们还未死心,思考能不能对 greet{"Swift"} 的写法进一步简化,这就要请出 @autoclosure 隆重登场, 对 greet() 的 name 参数前加上  @autoclosure, 就是

有了 @autoclosure 修饰之后,就像调用普通函数一样调用 greet(_:) ,直接传入字符, 看起来这个函数接收的是一个字符串。实质上在 @autoclosure 的作用下参数会被自动装箱成一个闭包, 也就把表达式映射到闭包过程由函数来完成。即 greet("Swift") 相当于 greet({"Swift"}), 但这个例子中有 @autoclosure 修饰的情况下是不能写成 greet({"Swift"}), 否则产生了二次装箱变成了 greet({{"Swift"}})

若说 @autoclosure 使得传入的值被延迟估值(延迟到函数中对闭包调用时求值)是不太恰当的,其本质是闭包固有的延迟估值属性, @autoclosure 的本质是帮你完成了表达式到闭包的映射,简单来讲就是用 @autoclosure 一次性的替换了每次调用时两边的大括号。

我们来扯 @autoclosure 达成了表达式的延迟估值有点跑偏,所以我们这里说闭包的延迟估值,且看示例

如果非闭包参数,我们知道求值是发生成函数调用前

关于闭包参数和延迟求值特性, 如果闭包有条件的被执行,并且表达式求值耗资源的话可以好好利用这一特性。

Apple 官方说 @autoclosure 可能会使得代码不易于理解,应在上下文或函数名中暗示出传入的参数会被延迟求值的 . 这里的顾虑也就是延迟求值可能会带来费解与潜在的 Bug, 比如

如果我们未仔细了解 foo(_:) 函数的原型,不清楚它的参数是 @autoclosure 的,我们会迷惑为什么前面 index++ 了, print(index) 输出的还是 0, 而我们在循环中经常会 foo(index++) 这么调用

顺便提一句,@autoclosure 默认为 @noescape 属性,如果想要 @autoclosure 为 escaping 属性,注解就合一块写成 @autoclosure(escaping)

既然本文标题中提到了 Scala 的 Call-By-Name 估值策略,那么还是以一个 Scala 的例子作为最生动的说明

执行

✗ scala Test.scala
1:
2: World

Scala 不需要像 Swift 的 @autoclosure 那样的注解,只需要把原来的参数声明 value: StringBuilder 中间加个 => 变成 value: => StringBuilder,就实现了 Swift 的 @autoclosure 的特性

本文链接 https://yanbin.blog/swift-learning-autoclosure-call-by-name/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments