分享

Scala 不建议用 return的原因是什么?

easthome001 发表于 2016-8-26 06:55:42 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 2 15321
本帖最后由 easthome001 于 2016-8-26 06:57 编辑

问题导读

1.Scala中是否默认返回值?
2.使用Return有哪些缺点?






Return没有用武之地
Scala和Ruby类似,Scala中将最后出现的变量作为return的值。
使用return必须显式指定返回类型,使Scala失去推断返回值类型的能力。
因此,没必要使用return关键字了。
而必须使用return的代码往往意味着存在“多路径返回”的问题,即return存在多个条件分支中,并借助return中断返回的特性来处理分支。《代码大全》中不建议这么做。因为
1 可读性往往不好
2 并不是非要这么做

而在Scala中,鼓励使用函数式的风格更加让Return显得没有用武之地。
因此,如果发现不得不使用return的时候,应该重新审视下流程是否应该改写,过多的嵌套循环是否能拆分成多个函数递归,lambda等形式


使用Return性能下降


实际上,有 return 的话,代码反而会变得不清晰。

简单地讲,函数式编程差不多追求的就是代码块(函数)写得和数学表示差不多,因为这样才叫清晰。比如在数学中:

[mw_shl_code=bash,true]f(x) = x + 1
[/mw_shl_code]

对应成Scala代码,差不多就是:


[mw_shl_code=bash,true]def f(x: Double) = x + 1
[/mw_shl_code]
除了 def 关键字和对参数的类型标记外,是完全一样的。

除了这种视觉上的不清晰,计算上也不清晰。如果是纯函数式(Pure function)的,是可以应用替换模型(?我不太确定,希望有人能补充)的。比如再有一个定义:
[mw_shl_code=bash,true]def g(y: Double, z: Double) = f(y) * f(z)
[/mw_shl_code]

那么就可以这样证明 g 是干什么的(?数理逻辑有形式化的方法,但是我早就忘光了,大概演示一下,意思意思,希望有人能补充):
[mw_shl_code=bash,true]g(y, z) = f(y) * f(z)
=> g(y, z) = f(x|y) * f(x|z) // 可以理解为*号左边的调用用y替代f的形式参数x,右边的调用用z替代f的形式参数y
=> g(y, z) = (x|y + 1) * (x|z + 1) //应用f
=> g(y, z) = (y + 1) * (z + 1) // 替代
=> g(y, z) = y * z + y + z + 1
[/mw_shl_code]

如果你用了 return ,你试试还能怎么证明 g 的正确性?不但难以证明,而且可能连 g 是干什么的都看不出来(上面这个例子太简单,我相信题主在实践中已经见过了不看函数名和注释就不知道是干什么的的函数)。补充:为什么需要证明呢?因为你要确保 g 正确。确保正确性,我知道的有两种办法:一是试验,列举出所有的合法输入,然后看结果是否都是符合预期的,这个目前看来不容易做到,比如上面的例子中你能列举出所有的 Double 类型值吗?二是证明,这个对程序本身有要求,还是参考Pure function。现在的软件测试一般是属于第一种方法,只不过使用了划分子集、取边界条件等等方法来降低难度,但是这样的话只能发现错误,不能证明正确性。

最后一点就是 return 在函数式编程中实际上是有特殊意义的,简单地讲(复杂的我也不会)就是 return 应该是"monad"(Monad (functional programming))的unit function,用Scala写出来应该是这样(这是伪代码,没有试过能不能编译、是否正确,如果有问题,纯属意料之中):


[mw_shl_code=bash,true]trait Monad[T[_]] {
  def return[A: T](a: A): T[A]
  def flatMap[A: T, B: T](a: T[A])(f: A => T[B]): T[B]
}[/mw_shl_code]

return 应该终结执行,直接返回结果,在 Scala 中是抛出异常然后 catch 。你写程序时写的

[mw_shl_code=bash,true]def h(x: Double) = {
  val a = f(x)
  val b = f(2)
  val c = f(a)
  f(c)
}[/mw_shl_code]

在函数式语言中,这是语言提供的特殊语法,将下面这个难看的东西给包装了一下(依然是伪代码,如果确实是这样,那一定是Scala编译器没做优化):
[mw_shl_code=bash,true]def h(x: Double) =
  try(f(x)).flatMap((a: Double) =>
    try(f(2)).flatMap((b: Double) =>
      try(f(a)).flatMap((c: Double) =>
        f(c)
      ))) match {
  case Success(value) => value
  case Failure(IAmAF_ckingReturnValue(value)) => value
  case Failure(t) => throw t
}
[/mw_shl_code]

可以参考这门公开课 Coursera - Free Online Courses From Top Universities 以及这门课中的这份PPT 。https://yunpan.cn/cMYzFjJCMqW4C  访问密码 b11e
我猜(只能用这个字了) return 抛出异常后, try 返回 Failure ,然后逐级传递上去。当然,这些实现细节都是我YY出来的,欢迎各位补充。我只是抛砖引玉罢了。



关于 return 语句,详见Scala语言规范文档Expressions,并仔细看看最后一段,摘抄如下:


If the return expression is itself part of an anonymous function, it is possible that the enclosing instance of fhas already returned before the return expression is executed. In that case, the thrownscala.runtime.NonLocalReturnException will not be caught, and will propagate up the call stack.

所以 return 还是不要用为好。

2016年7月22日更新:
有同事在代码中用了return语句,发现性能很差,移除掉return后性能有显著提升。


整理自知乎:jmu j等

已有(2)人评论

跳转到指定楼层
easthome001 发表于 2016-8-26 06:56:24
本帖最后由 easthome001 于 2016-8-26 06:59 编辑

两个原因:

1. 回避“副作用”.
2. 使用“表达式(expression)”,而非“语句(statement)”.

使用return意味着你可以在函数的任意位置结束函数。这样会产生“副作用”,函数没有因为逻辑分支而自然结束。
使用return表明你的函数是一个产生值的语句块,而不是一个值。前者使你的代码成为语句,后者使之成为表达式。

但是既然选择使用Scala,就最好遵循它的GuideLine,使用表达式来书写代码。

作者:孔牧
来源:知乎
回复

使用道具 举报

小姜 发表于 2016-9-28 18:37:03
感谢楼主分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条