为什么缺少 else 块会转换为函数的单元类型 return?

Why does the absence of an else block translate to Unit type return for a function?

我注意到 else if(r1 == 0 || divisors.tail.isEmpty || !divisors.tail.contains(r1)){newAcc} 行中存在类型不匹配问题。因为我的 if ... else if ...

没有 else 子句
def euclidianDivision(dividend:Int,divisor:Int):(Int,Int)={
  val quotient = dividend/divisor
  val remainder = dividend%divisor

  (quotient,remainder)
}
def firstExpansion(dividend:Int,divisors:List[Int]):List[(Int,Int)]={
  def firstExpansionIter(dividend:Int,divisors:List[Int], acc:List[(Int,Int)]):List[(Int,Int)]= {
    val div1:Int = divisors.head
    val (q1,r1):(Int,Int) = euclidianDivision(dividend,div1)
    val newAcc:List[(Int,Int)] = acc:::List((div1,q1))
    if (divisors.tail.contains(r1)){
      firstExpansionIter(r1,divisors.tail,newAcc)
    }else if(r1 == 0 || divisors.tail.isEmpty || !divisors.tail.contains(r1)){newAcc}
  }
  firstExpansionIter(dividend,divisors,List((0,0))).tail
}

这是错误代码:

Error:(32, 15) type mismatch; found : Unit required: List[(Int, Int)] }else if(r1 == 0 || divisors.tail.isEmpty || !divisors.tail.contains(r1)){newAcc}

我可以通过添加 else 子句来纠正这个问题,但是如果没有默认处理的结果,为什么函数会尝试 return a Unit?

N.B:更正代码:

def firstExpansion(dividend:Int,divisors:List[Int]):List[(Int,Int)]={
  def firstExpansionIter(dividend:Int,divisors:List[Int], acc:List[(Int,Int)]):List[(Int,Int)]= {
    val div1:Int = divisors.head
    val (q1,r1):(Int,Int) = euclidianDivision(dividend,div1)
    val newAcc:List[(Int,Int)] = acc:::List((div1,q1))
    if (divisors.tail.contains(r1)){
      firstExpansionIter(r1,divisors.tail,newAcc)
    }else if(r1 == 0 || divisors.tail.isEmpty || !divisors.tail.contains(r1)){newAcc}
    else throw new RuntimeException("Something unexpected happened.")
  }
  firstExpansionIter(dividend,divisors,List((0,0))).tail
}

I can correct this by adding the else clause, but how come if there is no outcome handled by default, the function tries to return a Unit?

在 Scala 中,与更多 "imperative" 语言不同,(几乎)一切都是表达式(很少有语句),并且每个表达式的计算结果都是一个值(这也意味着每个方法 return是一个值)。

这意味着,例如,条件表达式 if (condition) consequence else differentConsequence 是一个计算值的表达式。

比如在这段代码中:

val foo = if (someRandomCondition) 42 else "Hello"

表达式的 then 部分将计算为 42,表达式的 else 部分将计算为 "Hello",这意味着 if 表达式作为一个整体将计算为 42"Hello".

那么,foo 的类型是什么?那么,在 then 的情况下,值的类型是 Int,而在 else 的情况下,值的类型是 String。但是,这取决于 someRandomCondition 运行时值 ,这在编译时是未知的。因此,作为 whole if 表达式类型的唯一选择是 Int 的最低共同祖先(从技术上讲,weak least upper bound)和 String,即 Any.

在有联合类型的语言中,我们可以给它一个更精确的类型,即联合类型Int | String。 (Scala 3 has union types,所以我们可以给表达式这个确切的类型,尽管 Scala 3 不会推断联合类型。)在 Scala 3 中,我们甚至可以用更精确的类型 42 | "Hello" 来注释它,这是实际上是 TypeScript 将为等效条件表达式推断的类型:

const foo = someRandomCondition ? 42 : "Hello"

现在,让我们继续看问题中的代码:

val bar = if (someRandomCondition) 42

bar 的类型是什么?我们上面说它是 thenelse 分支类型的最低共同祖先,但是...... else 分支的类型是什么? else 分支的计算结果是什么?

记住,我们说过 每个表达式的计算结果都是一个值,所以 else 分支 必须 计算结果是某个值.它不能只评估为 "nothing".

这可以通过单位类型的so-called单位值来解决。单位值和类型称为 "unit" 值和类型,因为类型的设计方式使得它 只能容纳单个值 。单元类型没有成员,没有属性,没有字段,没有语义,什么都没有。因此,不可能将单位类型的两个值彼此区分开来,或者换句话说:单位类型只能有一个值,因为单位类型的另一个值 must 相同。

在许多编程语言中,单位值和类型使用与元组值和类型相同的表示法,并简单地用空元组来标识()。空元组和单位值是一回事:它们没有内容,没有意义。例如Haskell中,type和value都写成().

Scala也有单位值,也是写成()。但是,单位类型是 scala.Unit.

因此,单位值是一个无用的值,用于表示无意义的return值。

在某些命令式语言中,一个相关但不同的概念是 void 类型(或者在某些语言中,它更像是 "pseudo-type")。

请注意 "returns nothing" 不同于 "doesn't return",这将在本答案的第二部分变得重要。

所以拼图的前半部分是:Scala 语言规范说

if (condition) expression

等同于

if (condition) expression else ()

这意味着在(隐式)else 的情况下,return 类型是 Unit,它与 List[(Int, Int)] 不兼容,因此,你得到类型错误。

但是为什么抛出异常可以解决这个问题?

这给我们带来了 second 特殊类型:NothingNothing是一个so-called底层类型,这意味着它是每个类型的子类型。 Nothing 没有 有任何价值。那么,return类型的Nothing代表什么呢?

表示不return的表达式。我重复上面所说的内容:这与 return 没有任何区别

一个只有 side-effect return 什么都没有的方法,但是 它确实 return。它的 return 类型是 Unit,它的 return 值是 ()。它没有 有意义的 return 值。
具有无限循环或抛出异常的方法根本不会return。它的return类型是Nothing并且它没有return值

这就是为什么在 else 子句中抛出异常可以解决问题:这意味着 else 子句的类型是 Nothing,并且由于 Nothingevery type 的子类型,what then 子句的类型是什么并不重要,最常见的then 子句类型的超类型和 Nothing 总是 是 th 的类型then 子句。 (想一想:父亲和他的任何 children、祖父children、great-grandchildren 等的最低共同祖先永远是父亲本人。[的最低共同祖先=66=] 并且 T 的任何子类型将始终是 T。由于 Nothing 是所有类型的子类型,因此 T 和 [=51= 的最低共同祖先] 将永远是 T 因为 Nothing 总是 T 的子类型,不管 T 是什么。)