我写了一个简单的猜数字程序,我需要知道它是否有任何类型的递归,它是什么类型(原始/尾)(我是新来的所以请忍受我)
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!"
答案 0 :(得分:12)
如果函数调用自身,则函数是递归的(不一定是每种情况,但至少在一种情况下)。例如:
sum [] = 0
sum (x:xs) = x + sum xs
上面的函数不是尾递归的。在第二个等式中,首先计算x
和sum xs
,最终结果是它们的总和。由于最终结果不是对函数的调用,因此它不是尾递归的。要将此函数转换为tail递归,我们可以使用累加器模式:
sum [] acc = acc
sum (x:xs) acc = sum xs (x + acc)
现在请注意,在第二个等式中,首先计算xs
和x + acc
,然后作为最后一步调用自身。尾递归函数很重要,因为它们可以系统地转换为循环,从而消除了函数调用的开销。有些语言进行了这种优化,我认为在Haskell中这种优化是不必要的(参见下面的hammar评论)。
你的函数checkGuess看起来似乎是尾递归的,但事实并非如此。 do
语法是使用>>=
运算符的语法糖。
f = do
x <- g
h x
转换为
f = g >>= (\x -> h x)
因此,在几乎每个符号中,最后一个要调用的函数是>>=
。
如果函数可以使用描述为here的5个构造构造,则它是原始递归的。加法,乘法和阶乘是原始递归函数的例子,而阿克曼函数则不是。
这在可计算性理论中通常很有用,但就编程而言,通常不关心(编译器不会尝试对它做任何事情)。
注意:
答案 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被递归调用之后你什么都不做。所以你的尾部递归是一种方式。