下面,sumAllIf是尾遞回的,sumAllFold不是。但是,sumAllIf實際上具有相同的實作。這是 Scala 編譯器(或 Scala 庫)的缺點,還是我忽略了什么?
def maybeNext(in: Int): Option[Int] = if in < 10 then Some(in 1) else None
// The Scala library implements Option.fold like this:
// @inline final def fold[B](ifEmpty: => B)(f: A => B): B =
// if (isEmpty) ifEmpty else f(this.get)
@annotation.tailrec
def sumAllIf(current: Int, until: Int, sum: Int): Int =
val nextOption = maybeNext(current)
if (nextOption.isEmpty) sum else sumAllIf(nextOption.get, until, sum nextOption.get)
// However, with Scala 3.1.0 and earlier, this is NOT tail recursive:
def sumAllFold(current: Int, until: Int, sum: Int): Int =
maybeNext(current).fold(sum)(next => sumAllFold(next, until, sum next))
@main def main(): Unit =
println(sumAllIf(0, 10, 0))
println(sumAllFold(0, 10, 0))
該問題類似于帶有 fold 的問題 Scala @tailrec,但在這里我想了解為什么以及將來是否可以支持。
該示例適用于 Scala 3.1,但問題本身也適用于 Scala 2.x。
uj5u.com熱心網友回復:
遞回呼叫發生在 lambda 內部。所以它不是尾遞回呼叫,除非編譯器將折疊和 lambda 行內到您自己的方法中,然后才測驗它是否是尾遞回。但是編譯器不會自動執行此操作,并且可能永遠不會自動執行此操作。
好訊息是,在 Scala 3 中,您可以很容易地解決這個問題,而且從理論上講,標準庫可能會被調整以利用這一點。它所需要的只是顯式地實作fold為帶有行內引數的行內方法。
inline def fold[A, B](opt: Option[A])(inline onEmpty: B)(inline f: A => B): B =
opt match
case Some(a) => f(a)
case None => onEmpty
@annotation.tailrec
def sumAllFold(current: Int, until: Int, sum: Int): Int =
fold(maybeNext(current))(sum)(next => sumAllFold(next, until, sum next))
請注意,行內引數自動具有按名稱語意,因此onEmpty在不將型別更改為=> B.
uj5u.com熱心網友回復:
下面,
sumAllIf是尾遞回的,sumAllFold不是。但是,sumAllIf實際上具有相同的實作。這是 Scala 編譯器(或 Scala 庫)的缺點,還是我忽略了什么?
這就是尾遞回的簡單定義。尾呼叫是子例程中的最后一次呼叫。遞回是子例程呼叫自身的時候。尾遞回是當尾呼叫是遞回呼叫時,或者當遞回呼叫位于尾部位置時 - 換句話說,它是當子例程將自身作為最后一個呼叫呼叫時。
在您的情況下,最后一個電話是 to fold,而不是 to sumAllFold。
這不是 Scala 編譯器或 Scala 庫的缺點。sumAllFold不是尾遞回,因為它的尾呼叫不是遞回呼叫,遞回呼叫不是尾呼叫。換句話說,它不是尾遞回,因為它根本不是尾遞回。
這與問你的藍色車不是黃色是否是你的機械師的缺點基本相同。它不是。你的藍色車不是黃色的,因為它根本不是。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/422313.html
標籤:
下一篇:將泛型定義為案例類
