(原文:Redefining Everything with the Swift REPL 译者:Micheal Geng)
(译者注:REPL即为Read-Eval-Print Loop,中文译为“读取-求值-输出”循环,这里是指swift编程时的实时代码预览功能,即在屏幕左侧输入代码时,在右侧屏幕实时演示出代码执行效果。这就使得在编程开发过程中,开发人员可以快速的向前或向后预览代码,而各个环节的代码实现效果也会进行实时的展示,以此即可测试自己所开发的程序在各个环节的反应,如图1。)
在上一篇关于REPL的文章(中译版)中,我们向大家展示了如何使用REPL 来更好的学习Swift。而在本文中,我们将探讨REPL如何颠覆普通的编码规则,为您提供一种全新的编程体验。
重定义标识符(Identifiers)
Swift编译器会自动的纠正一些常见的编程错误,比如对标识符进行重复定义会进行报错,就像下面的代码:
swiftc - var x = "The Answer" var x = 42 ^D error: invalid redeclaration of 'x'
这种错误在非交互式编程中是合理的,但是在实时性交互环境下,能简单改变变量值是很有用的。Swift REPL对此做了专门的设计,就像下面代码演示的这样:
1> var x = "The Answer" x: String = "The Answer" 2> var x = 42 x: Int = 42 3> x + 10 $R0: Int = 52
就像上面代码所显示的,新的定义将会取代之前的定义,即使这个定义的类型发生了变化(由 String型变为Int型)。而且它还可以通过迭代优化来支持更多的数据进行试验。比如,你可以设计一个递归函数来实现它:
func fib(index: Int) -> Int { if index < = 1 { return 1 } return fib(index - 1) + fib(index - 2) } fib(40) $R1: Int = 165580141
这只是这个函数的一种写法,你可以尝试使用其他不同的算法或API写法来验证。REPL让算法的改进更容易,比如下面的代码:
func fib(index: Int) -> Int { var lastValue = 1 var currentValue = 1 for var iteration = 2; iteration < = index; ++iteration { let newValue = lastValue + currentValue lastValue = currentValue currentValue = newValue } return currentValue } fib(40) $R2: Int = 165580141
这段代码使用和上面同样的表达式,但执行的是新的函数。虽然这个例子很简单,但它体现了一些REPL的设计,能简化递归或迭代的实现。
重定义,还是重载(Overload)?
REPL能重新定义常量、变量和类型,而且正如上面所看到的,它也重新定义了函数。那么问题来了:它如何与函数重载相结合呢?事实上,REPL将只取代名称和方法签名相同的函数,就像上面斐波那契数列求和的例子一样。如果函数具有相同的名称,但方法签名不同时,不会取代之前的定义,只会定义一个新的重载函数。Swift支持仅返回值类型不同的两个函数的重载。比如下面这个例子:
func foo() { println("Foo!") } func foo() -> String { return "Foo!" } foo() error: ambiguous use of 'foo'
上面定义了两个名称相同但实现不同的函数,它们的调用需要注意,如果没有指定具体是哪个函数,就会报错,你需要在调用时指定需要的返回值类型:
var foo: String = foo() String = "Foo!" foo() as Void Foo!
智能识别定义
重新定义标识符的功能十分强大,但是它只能应用在标识符被申请之后,任何一行通过实时性编译过的代码中任何引用都将保持在它之前的最新定义。这就说明新的定义会遮盖住旧的定义,但旧的定义并不会消失,(译者注:每个标识符被定义后该作用域就开始,直到新的定义来临之前该作用域结束,并开始新的作用域。)下面的例子将会说明这点在实践中是如何应用的:
var message = "Hello, World!" message: String = "Hello, World!" func printMessage() { println(message) } printMessage() Hello, World! message = "Goodbye" printMessage() Goodbye var message = "New Message" printMessage() Goodbye println(message) New Message
要了解这里发生了什么,我们可以一行一行的看代码。30行申请了一个变量,在31-33行定义了printMessage()方法,实现打印该变量,在34行调用该方法并产生了我们预期的结果。到目前为止,一切看起来很简单。
但是,从35行开始出现一些不同,35行为30行中申请的变量赋新值,在36行打印时得到了预期的结果。但另一方面,37行声明一个具有相同名称的变量时,本来应该从此处隐藏原始变量的值,但在38行调用函数的定义时发现原始变量的值被打印出来了,但却并没有显示新变量的值,这是因为在重定义之前printMessage()已经连同参数一起被编译了,所以重定义参数对此函数不生效。但是第39行显示,新定义的变量可以被引用,就像我们预期的那样。
所有的重定义标示符都是以这种方式工作,无论是函数、变量或者是类型。REPL给予重定义标识符很大的自由,与之相对的,优先引用(prior references)则在强大的语义检查之下被实时的编译。在上面这个例子中,如果message被重定义为一种类型而非变量会发生什么?printMessage函数将不会被编译。REPL不要求开发者无休止的分类整理各种标识符等边缘化内容,它始终坚持内部的一致性。