代码之家  ›  专栏  ›  技术社区  ›  fuz

如果我有一个表达式,它是可部分计算的,那么避免尾部递归是一个好主意吗?

  •  1
  • fuz  · 技术社区  · 15 年前

    考虑一个haskell表达式,如下所示:(小例子,不要告诉我明显的方法是什么!;)

    toBits :: Integral a => a -> [Bool]
    toBits 0 = []
    toBits n = x : toBits m where
     (m,y) = n `divMod` 2
      x    = y /= 0
    

    由于此函数不是尾部递归函数,因此也可以编写:

    toBits :: Integral a => a -> [Bool]
    toBits = toBits' [] where
      toBits' l 0 = l
      toBits' l n = toBits (x : l) m where
        (m,y) = n `divMod` 2
         x    = y /= 0
    

    (我希望这句话没有什么不对劲的)

    我想问的是,这些解决方案中哪一个更好。第一种方法的优点是,可以很容易地对其进行局部评估(因为haskell在第一种方法时停止 : 不需要),但是第二个解决方案(显然)是尾部递归的,但是在我看来,它需要被完全评估,直到你能得到一些东西。

    它的背景是我的brainfuck解析器(见我的优化问题),它的实现非常糟糕(各种各样) reverse 说明…但是可以在第一种方法中轻松实现——让我们称之为“半尾部递归”。

    3 回复  |  直到 15 年前
        1
  •  1
  •   Reid Barton    15 年前

    让我重命名第二个版本,并修正一些打字错误,这样您就可以测试函数了。

    toBits :: Integral a => a -> [Bool]
    toBits 0 = []
    toBits n = x : toBits m where
     (m,y) = n `divMod` 2
     x     = y /= 0
    
    toBits2 :: Integral a => a -> [Bool]
    toBits2 = toBits' [] where
      toBits' l 0 = l
      toBits' l n = toBits' (x : l) m where
        (m,y) = n `divMod` 2
        x     = y /= 0
    

    这些函数实际上并不计算相同的东西;第一个函数产生一个以最低有效位开始的列表,而第二个函数则以最高有效位开始。换言之 toBits2 = reverse . toBits 事实上 reverse 可以使用与您在 toBits2 .

    如果你想要一个从最低有效位开始的列表, toBits 是好的哈斯克尔风格。它不会产生堆栈溢出,因为递归调用包含在(:)构造函数中,而该构造函数是懒惰的。(而且,你不能在 托比特 通过提前强制结果列表的延迟元素的值,因为在第一种情况下需要计算参数 toBits 0 = [] 以确定列表是否为空。)

    如果您想要一个从最高有效位开始的列表,要么写 托比特2 直接或定义 托比特 并使用 reverse . toBits 可以接受。我可能更喜欢后者,因为在我看来,它更容易理解,而且你不必担心你是否需要重新实现 颠倒 将导致堆栈溢出。

        2
  •  2
  •   MtnViewMark    15 年前

    我想你一切都很好。第一种形式通常更好,因为在完成计算之前可以从中获得有用的输出。这意味着,如果在另一个计算中使用了“tobits”,编译器可能会将它们组合起来,而作为“tobits”输出的列表可能根本不存在,或者一次只存在一个cons单元格。很高兴第一版也更清晰易读!

        3
  •  2
  •   Anthony    15 年前

    在哈斯克尔,你的第一选择通常是首选(我会说“总是”,但你总是错的,当你使用这个词)。当输出可以 递增消耗(例如递增计数器)。