2014-11-24 57 views
1

我知道至少有兩種寫尾部遞歸函數的樣式。以一個sum功能,例如:尾遞歸:內部「循環」函數或累加器的默認值

def sum1(xs: List[Int]): Int = { 
    def loop(xs: List[Int], acc: Int): Int = xs match { 
    case Nil => acc 
    case x :: xs1 => loop(xs1, acc + x) 
    } 
    loop(xs, 0) 
} 

VS

def sum2(xs: List[Int], acc: Int = 0): Int = xs match { 
    case Nil => acc 
    case x :: xs1 => sum2(xs1, x + acc) 
} 

我注意到的第一個樣式(內循環功能)較爲普遍要比第二。有什麼理由更喜歡它,或者只是一個風格問題的區別?

+1

有很好的理由,以避免違約的論點大部分時間(見[此評論](HTTPS ://github.com/typelevel/wartremover/issues/116#issuecomment-5128034 4)和其餘的線程)。 – 2014-11-24 18:31:26

+5

默認參數值在scala歷史中是相對較新的,這可以解釋表單1更常見。但也存在將額外參數作爲公共接口的一部分的問題,這可能很奇怪。 – 2014-11-24 18:31:40

回答

1

有幾個理由喜歡第一個符號。

首先,你清楚地向你的讀者定義從外部實現的內部實現。其次,在你的例子中,種子值是非常簡單的,你可以直接作爲默認參數,但是你的種子值可能是一個非常複雜的計算對象,它需要比默認值更長的初始值。例如,如果此初始化需要異步完成,那麼您肯定希望將其從默認值中刪除,並使用期貨或w/e進行管理。最後,正如Didier所說,sum1的類型是List [Int] - > Int(這很有意義)中的函數,而sum2的類型是來自(List [Int],Int)的函數 - >詮釋這是沒有意義的。而且,這意味着將sum1傳遞給sum2比較容易。例如,如果你有一個封裝詮釋的列表的對象,你要在它提供合成功能,你可以做(​​僞代碼,我沒有REPL將它妥善寫信跟):

class MyFancyList[T](val seed: List[T]) = { 
    type SyntFunction = (List[T] => Any) 

    var functions = Set[SyntFunction] 

    def addFunction(f: SyntFunction) = functions += f 

    def computeAll = { 
    for { 
     f <- functions 
    } 
    yield { 
     f(seed) 
    } 
    } 
} 

你可以這樣做:

def concatStrings(list:List[Int]) = { 
    val listOfStrings = for { 
    n <- list 
    } 
    yield { 
    n+"" 
    } 
    listOfStrings.mkString 
} 
val x = MyFancyList(List(1, 2, 3)) 
x.addFunction(sum1) 
x.addFunction(concatStrings) 
x.computeAll == List(6, "123") 

,但你不能添加SUM2(不容易,至少)