当前位置: 首页 > 编程笔记 >

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

靳涵亮
2023-03-14
本文向大家介绍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使用可变状态的。

 类似资料:
  • 我在scheme中构建了一个递归函数,它将在一些输入上重复给定的函数f,n次。 我需要用尾递归构建这个函数的迭代版本,如果我正确理解尾递归,我认为我做得对。 我的问题是,这真的是迭代的吗?我相信我已经使用尾部递归正确地构建了它,但从技术上讲,它仍然将一系列操作推迟到count=0,在这里,它执行叠加的任意多个组合。

  • 我对函数式编程很陌生,尤其是下面使用的Scheme。我正在尝试使以下函数是递归的,尾递归的。基本上,该函数的作用是对两个字符串的对齐方式进行评分。当给定两个字符串作为输入时,它会比较每个“列”字符,并根据在称为 scorer 的函数中实现的评分方案(由下面的代码中的函数调用)来累积该对齐的分数。 我有一个想法,用一个帮助函数来累积分数,但我不太确定如何去做,因此我该如何让下面的函数尾递归呢?

  • 尾递归优化 recur 尾递归优化是函数式编程不能缺少的一个性能优化方案. 没有尾递归, 常有的递归调用也会形成很深的调用栈消耗内存. cljs 和 Clojure 类似, 都需要通过声明 recur 进行优化. 最终代码会被编译为 white 结构的 js 代码,从而提高性能. (defn factorial [acc n] (if (<= n 1) acc (recur (* ac

  • 正如我在最近的一个SO问题中提到的,我正在通过学习Euler问题来学习F。 现在,我对问题3有了一个有效的答案,如下所示: 但是,由于有2个执行路径可能导致对的不同调用,我不确定它是否可以针对尾递归进行优化。所以我想出了这个: 由于只有一条路径会导致对findLargestPrimeFactor的尾部调用,我认为它确实会针对尾部递归进行优化。 所以我的问题是: 即使有两个不同的递归调用,第一个实现

  • 我有一个家庭作业问题,它给出了一个递归函数,我必须使用尾部递归来实现它。函数为f(0)=1 f(n)=1 2*f(n-1) 我并不擅长尾部递归,我试着查找示例,但我发现的都是没有斐波那契序列的示例,这没有多大帮助。 我真正拥有的是 我知道尾递归基本上每次调用都计算函数,我只是不知道如何实现它。 编辑:我打了一个错字f(n)应该是1 2*f(n-1)

  • 我试图了解如何将各种递归函数转换为尾递归。我已经查看了许多将斐波那契和阶乘转换为尾递归的示例,并理解了这些示例,但很难跳到具有某种不同结构的问题。一个例子是: 如何将其转换为尾部递归实现? 我已经看过类似的问题,例如:将正常递归转换为尾部递归,但这些似乎并没有转化为这个问题。

  • 如果说在任何地方都使用递归,那么可以使用for循环,对吗?如果递归通常比较慢,那么将其用于循环迭代的技术原因是什么? 如果总是可以将递归转换为for循环,那么有经验法则吗?

  • 问题内容: 我正在编写一个递归函数,其目的是迭代pList文件。我的代码是 但是当我调用函数“ HashMapper((Map)((Map)entry).keySet());”时。我有一个例外 java.util.HashMap $ HashMap条目不能转换为java.util.Map 我不知道如何调用函数以及如何将Hashmap条目转换为Map 问题答案: 入境确实不是。它是,因此您可以根据需