为何Scala允许在Try.recoverWith中使用return语句?
这问题确实容易让人困惑,我来帮你拆解一下为什么第二个示例能编译,以及背后的执行逻辑问题。
为什么示例2的代码是合法的(能通过编译)?
要理解这一点,我们需要从Scala的类型推断和return的特性两个角度来看:
return的非局部返回与类型推断
Scala中的return是非局部返回,它的作用是直接跳出当前所在的带命名方法(这里就是result方法),而不是仅仅跳出当前的匿名函数/偏函数。当你在偏函数里写return Some(-1)时,编译器知道这个偏函数的代码永远不会执行到末尾(因为return直接终止了执行),因此会推断这个偏函数的返回类型为Nothing——而Nothing是Scala中所有类型的子类型,自然也符合recoverWith要求的PartialFunction[Throwable, Try[U]]类型(因为Try[Nothing]是Try[Int]的子类型,而U在这里是Int,满足U >: T的约束)。方法返回类型的明确指定
你给result方法明确指定了返回类型Option[Int],而return Some(-1)的类型正好是Option[Int],与方法的返回类型匹配。编译器在检查时,会确认return的表达式类型符合方法的返回要求,同时偏函数的返回类型(Nothing)也满足recoverWith的参数类型约束,因此编译可以通过。
对比你去掉.toOption的情况:此时方法f的返回类型需要被推断为Try[U],但return Some(-1)的类型是Option[Int],与Try[U]不兼容,编译器无法完成类型推断,因此报错。
为什么示例2的执行结果不符合预期?
这部分的核心是return在非inline方法参数中的行为:
Scala的非局部返回只有在匿名函数被inline方法调用时,才能正确地直接返回到外层方法。而Try.recoverWith并不是一个inline方法,因此当你在偏函数里执行return Some(-1)时,这个return无法直接跳出result方法,反而会抛出一个特殊的NonLocalReturnControl异常。
而Try的机制会捕获所有Throwable类型的异常,这个NonLocalReturnControl异常也不例外——它会被转化为一个Failure实例,后续调用.toOption就会得到None,这就是你看到的结果。
如果想要达到预期的效果,你应该遵循recoverWith的契约,返回一个Try实例,而不是使用return:
def result: Option[Int] = Try(0/0).recoverWith { case _ => Success(-1) }.toOption
内容的提问来源于stack exchange,提问作者prayagupadhyay

