12.5 函数的结果

我在前面已经讲了函数结果的生成和返回。我们再一起来简单地回顾一下。

Julia 程序中的函数总会有结果值。不过,当一个函数的结果值为nothing时,我们一般会忽略掉它,并认为该函数没有返回(具有实际意义的)结果值。Julia 函数的结果值有且只能有一个。但是,我们却可以从函数中同时返回多个独立的结果值。在这种情况下,Julia 会用一个元组把这些结果值包装起来,并把这个元组返回给函数的调用方。这显然是一种很好的变通。也正因为如此,函数的结果声明变得非常简单。我们如果要为一个函数添加结果声明,那么只需要编写一个类型声明以指定其结果的类型就可以了。对于实际上会有多个结果值返回的函数,我们可以把其结果的类型声明为Tuple。当然,若为了灵活性,我们也可以不加结果声明,但最好在函数的注释中对其结果加以解释。

在缺省情况下,Julia 会把函数实际执行的流程中的最后一个表达式的求值结果作为这个函数的结果值。在这时,函数实际执行的流程是什么就显得尤为重要了。如果一个函数中的流程比较复杂,比如拥有很多分支和一些特殊的情况,那么它的可读性就一定会变得很差。这时,我通常就会显式地指明结果值,而不会让 Julia 自己去识别。这倒不是为 Julia 对代码的解析提供方便,而是为了代码的阅读者和维护者考虑。

要想显式地为函数指明结果值,就需要使用return语句。return语句以return关键字为起始,后面可以携带需要返回的一个或多个结果值。Julia 在执行函数的过程中一旦碰到return语句就会马上结束对当前函数的执行,并将流程的控制权返还给函数的调用方。也就是说,Julia 会从调用这个函数的表达式那里继续执行后续的代码。

如果return语句携带了结果值,那么结果值也会随着控制权返回到函数的调用方。当然了,函数的结果值也可以由表达式代表。如果是这样,那么 Julia 就会先对这里的表达式进行求值,然后再将计算出来的结果值和控制权一并返回给函数的调用方。

我们在前面几章其实已经用到过return语句,比如在cmp函数的新衍生方法中,又比如在get_bmi函数中。下面,我们就以cmp函数的新衍生方法为例:

  1. function cmp(A::Array, B::Array)
  2. for (a, b) in zip(A, B)
  3. if !isequal(a, b)
  4. return isless(a, b) ? -1 : 1
  5. end
  6. end
  7. return cmp(length(A), length(B))
  8. end

你还记得吗?cmp函数会比较参数A和参数B的值,并返回-101以表明比较的结果。

这里的cmp方法先调用了zip函数,把在AB中的处在对应位置上的元素值都两两组对,并按照原有的顺序放到一个新的可迭代对象中。因此,for语句中的迭代变量ab会依次地代表源于参数AB的一对对元素值。

显然,这个cmp方法要做的事情是依次地比较每一对元素值。一旦发现某对元素值不相等,它就会立即通过return语句把比较结果返回给它的调用方。这里有两点需要注意。第一点,这条return语句携带的正是一个表达式。所以,Julia 会先去对这个表达式进行求值,也就是判断ab谁大谁小并依此给出一个整数,然后再中止函数的执行并返回。第二点,虽然这条return语句处在一个条for语句之中,但是不论这条for语句正在进行第几次迭代,只要条件满足 Julia 就会中止它的执行。也就是说,return语句的执行是与流程的上下文无关的,正所谓快刀斩乱麻。也正因为如此,函数中的流程越是复杂,return语句所起到的作用就越是明显。

我们再来看处于cmp方法中的那条最后的return语句。其中的关键字return其实是可有可无的。因为如果两个参数值在对应位置上的元素值都两两相等,那么即便等到for语句的执行完全结束了也肯定得不出一个比较结果。这时,这条return语句就是cmp方法实际执行的流程当中的最后一条语句。因此,即使我们不写return关键字,表达式cmp(length(A), length(B))的求值结果也会被作为这个cmp方法的结果值。

好了,如果你理解了我在前面所讲述的这些内容,那么就应该可以掌握return语句的用法以及与函数的结果有关的知识了。顺便说一句,return语句只能够用在函数里。