F# 使用尾递归进行有效的迭代


本文向大家介绍F# 使用尾递归进行有效的迭代,包括了F# 使用尾递归进行有效的迭代的使用技巧和注意事项,需要的朋友参考一下

示例

从命令式语言未来许多开发商不知道怎么写了for-loop退出事件早F#不支持break,continue或者return。答案F#是使用尾递归,这是一种灵活且惯用的迭代方式,同时仍提供出色的性能。

假设我们要实现tryFind的List。如果F#支持的话,return我们可以这样写tryFind:

let tryFind predicate vs =
  for v in vs do
    if predicate v then
      return Some v
  None

这不适用于F#。相反,我们使用尾递归编写函数:

let tryFind predicate vs =
  let rec loop = function
    | v::vs -> if predicate v then 
                   Some v 
               else 
                   loop vs
    | _ -> None
  loop vs

尾递归是有效的,F#因为当F#编译器检测到函数是尾递归时,它将重写递归为有效的while-loop。使用ILSpy我们可以看到这对于我们的函数是正确的loop:

internal static FSharpOption<a> loop@3-10<a>(FSharpFunc<a, bool> predicate, FSharpList<a> _arg1)
{
  while (_arg1.TailOrNull != null)
  {
    FSharpList<a> fSharpList = _arg1;
    FSharpList<a> vs = fSharpList.TailOrNull;
    a v = fSharpList.HeadOrDefault;
    if (predicate.Invoke(v))
    {
      return FSharpOption<a>.Some(v);
    }
    FSharpFunc<a, bool> arg_2D_0 = predicate;
    _arg1 = vs;
    predicate = arg_2D_0;
  }
  return null;
}

除了一些不必要的分配(希望能消除JIT-er的分配)之外,这实际上是一个有效的循环。

另外,尾递归是惯用的,F#因为它使我们避免了可变状态。考虑一个将一个sum元素中的所有元素求和的函数List。一个明显的第一尝试是:

let sum vs =
  let mutable s = LanguagePrimitives.GenericZero
  for v in vs do
    s <- s + v
  s

如果将循环重写为尾递归,则可以避免发生可变状态:

let sum vs =
  let rec loop s = function
    | v::vs -> loop (s + v) vs
    | _ -> s
  loopLanguagePrimitives.GenericZerovs

为了提高效率,F#编译器将其转换为while-loop使用可变状态的。