我很困惑什么是递归,尾递归,原始递归和什么不是

时间:2012-02-04 05:55:58

标签: haskell recursion functional-programming tail-recursion

我写了一个简单的猜数字程序,我需要知道它是否有任何类型的递归,它是什么类型(原始/尾)(我是新来的所以请忍受我)

module MyProgram where
import System.Random

guessNum :: IO()
guessNum =
do  --gen <- newStdGen
    --let num = randoms gen :: [Int]
    num<-randomRIO(20,100 :: Int)
    putStrLn "Guess the number: "
    input<-getLine
    let guess = (read input :: Int)
    checkGuess guess num


checkGuess:: Int -> Int -> IO()
checkGuess guess num1 |(num1<guess)=
                do  putStr"Too high! Guess again!"
                    input<-getLine
                    let guess = (read input)::Int
                    checkGuess guess num1
                |(num1>guess)=          
                do  putStr"Too low! Guess again!"
                    input<-getLine
                    let guess = (read input)::Int
                    checkGuess guess num1
                |(num1==guess)  =
                do  putStr"Congratulations! You found the number!"

3 个答案:

答案 0 :(得分:12)

如果函数调用自身,则函数是递归的(不一定是每种情况,但至少在一种情况下)。例如:

sum [] = 0
sum (x:xs) = x + sum xs

上面的函数不是尾递归的。在第二个等式中,首先计算xsum xs,最终结果是它们的总和。由于最终结果不是对函数的调用,因此它不是尾递归的。要将此函数转换为tail递归,我们可以使用累加器模式:

sum [] acc = acc
sum (x:xs) acc = sum xs (x + acc)

现在请注意,在第二个等式中,首先计算xsx + acc,然后作为最后一步调用自身。尾递归函数很重要,因为它们可以系统地转换为循环,从而消除了函数调用的开销。有些语言进行了这种优化,我认为在Haskell中这种优化是不必要的(参见下面的hammar评论)。

你的函数checkGuess看起来似乎是尾递归的,但事实并非如此。 do语法是使用>>=运算符的语法糖。

f = do
    x <- g
    h x

转换为

f = g >>= (\x -> h x)

因此,在几乎每个符号中,最后一个要调用的函数是>>=

如果函数可以使用描述为here的5个构造构造,则它是原始递归的。加法,乘法和阶乘是原始递归函数的例子,而阿克曼函数则不是。

这在可计算性理论中通常很有用,但就编程而言,通常不关心(编译器不会尝试对它做任何事情)。

注意:

  • 可以说一组函数是相互递归的,如果它们相互调用的方式有周期(f调用g,g调用h和h最终调用f)。

答案 1 :(得分:1)

如果函数在其代码中的任何位置调用自身,则该函数是递归的。所以guessNum不是(在guessNum代码或它调用的代码中没有调用guessNum),而checkGuess是。

尾递归是递归调用是函数做的最后一件事...但这是Haskell和尾递归是一个主要用于严格语言的术语,它允许优化递归函数,使其不会增长堆栈(当前调用可以直接替换为递归调用,因为在递归调用返回后您不需要执行任何操作)。所以正如其他人所说checkGuess不是尾递归或者不是严格的语言......但是对于懒惰的语义,(a&gt;&gt; b)将被评估为许多Monads(包括IO)中的b a被评估(或者说IO动作已经完成),它可以被遗忘,并且b的返回是唯一重要的事情。

在简单地说,你的函数checkGuess是递归的,不是尾递归的最正式的定义,但这些定义并不适用于非严格的语言,如Haskell中,并checkGuess肯定会在不断的空间,如果它是尾部被执行以严格的语言递归(至少在Haskell的合理实现中,例如GHC)。

原始递归是在N ^ k上定义的概念 - &gt; N函数,我不认为这个问题对于checkGuess这样的函数是有意义的,不是没有一些可疑的适应,并且用一些更简单的语言(lamda-calculus等价)查看函数的翻译,这意味着制作显式的IO语义和所以...我会说,虽然你的函数对其Int参数没有任何作用,而原始递归函数是不可能的。

请注意,您的代码会自行重复,也许应该真正抽象出来的部分是:

input<-getLine
let guess = (read input :: Int)
checkGuess guess num

答案 2 :(得分:0)

尾递归就是在函数调用自身后什么都不做的时候 这通常通过返回下一个递归调用来完成。

因此,在你的checkGuess被递归调用之后你什么都不做。所以你的尾部递归是一种方式。

相关问题