【问题标题】:Support native recursion支持本机递归
【发布时间】:2017-06-07 19:00:24
【问题描述】:

我读到一篇关于 Haskell 中递归的文章说:

但是 lambda 演算并没有出现在表面上 由于表达式的匿名性,递归的方式。怎么做 你叫什么没有名字?能够编写递归 然而,函数对于图灵完备性是必不可少的。我们使用一个 组合器——称为 Y 组合器或定点组合器—— 在 lambda 演算中编写递归函数。 Haskell 有原生的 基于与 Y 组合器相同原理的递归能力。

本机递归是什么意思?

考虑以下代码sn-p:

applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b
applyTimes 0 f b = b
applyTimes n f b = f (applyTimes (n-1) f b)

上面的代码不符合Y combinator的原则,因为applyTimes是在函数体本身调用的,之前没有定义。
如果我错了,请证明我的回答。

【问题讨论】:

  • 文章链接?
  • 文章出自本书haskellbook.com
  • 我复制了这篇文章,请考虑。
  • “Haskell 具有本机递归能力”似乎是关键。

标签: haskell recursion


【解决方案1】:

“本机递归”是指语言本身支持递归定义。与所有术语都是匿名的基本 lambda 演算不同,Haskell 确实有一种方法来命名表达式。此外,在定义名称时,您可以使用该名称本身。您自己观察到了这一点:在定义 applyTimes 时,您使用了名称 applyTimes,因此利用了 Haskell 对递归的本机支持。

您还可以想象一种支持命名表达式但不支持递归的不同语言;实际上,许多函数式语言已经分别区分了递归和非递归定义的“letrec”和“let”形式。在这种语言中,如果applyTimes 使用“let”形式,您的定义将被拒绝,如果使用“letrec”形式,则会被接受。

【讨论】:

  • 你能解释一下“letrec”和“let”吗?
【解决方案2】:

“本机递归”只是意味着 Haskell 将递归作为一种“本机”(内置)语言功能,以递归定义和let 绑定的形式:

-- Recursive definition
map f (x : xs) = f x : map f xs
                       ---
map _ [] = []

-- Recursive “let” binding
main = let ones = 1 : ones in print (take 10 ones)
                      ----

在内部,编译器可以使用定点组合器 (fix) 重写这些,例如作为类型检查之前的简化,但我不知道它是否真的这样做。

map f xs = let
  map' k (x : xs) = f x : k xs
  map' _ [] = []
  in fix map' xs

main = let
  ones = fix ones'
  ones' k = 1 : k
  in print (take 10 ones)

在 Haskell 中,定点组合器是使用对递归绑定的原生支持来实现的:

-- Naïve implementation
fix f = f (fix f)

-- Optimisation to improve sharing
fix f = let x = f x in x

任何递归函数都可以用fix表示:

applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b
applyTimes 0 f b = b
applyTimes n f b = f (applyTimes (n-1) f b)

applyTimes n f b = let
  applyTimes' _ 0 = b
  applyTimes' k n = f (k (n - 1))
  in fix applyTimes' n

请注意,没有本机递归,Y 组合子 \ f -> (\ x -> f (x x)) (\ x -> f (x x)) 不能用简单类型的 lambda 演算表示,因为 x 没有类型可以让它成为一个函数x 作为参数。具体来说,它没有通过“发生检查”:如果x 是一个函数,那么它必须具有a -> b 形式的类型,但如果x 作为参数传递给x,那么它必须具有类型a。显然a不能等于a -> b,因为a出现在a -> b中,所以它会无限扩展:a -> b(a -> b) -> b((a -> b) -> b) -> b等等。

虽然 Haskell 的类型系统比 STLC 更强大,但类型化函数式语言通常将递归作为语言的原始部分而不是定点组合器。

【讨论】:

  • “在内部,编译器可以使用定点组合器 (fix) 重写这些,例如作为类型检查之前的简化,但我不知道它是否真的这样做。”那将是一个相当大的复杂化,我不知道它是如何工作的。当然,let 不能总是在 Haskell 中转换为 lambda,尽管在 GHC Core 的命令式 System FC 中这可能是可能的。 GHC 肯定不会在任何地方进行这样的翻译。它使用letletrec 直到代码生成。
  • 您能详细解释一下let bindings吗?
【解决方案3】:

上面的代码不符合Y combinator原则,因为applyTimes是在函数体本身调用的,之前没有定义。

这样的事情呢?

fix :: (a -> a) -> a
fix f = f (fix f)

applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b
applyTimes n f b = fix go n where

  go rec n = if n == 0 then b
             else f (rec (n - 1))

【讨论】:

    猜你喜欢
    • 2011-04-08
    • 1970-01-01
    • 2021-12-25
    • 2016-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-07
    相关资源
    最近更新 更多