考虑以下两个变体:
myReadListTailRecursive :: IO [String]
myReadListTailRecursive = go []
where
go :: [String] -> IO [String]
go l = do {
inp <- getLine;
if (inp == "") then
return l;
else go (inp:l);
}
myReadListOrdinary :: IO [String]
myReadListOrdinary = do
inp <- getLine
if inp == "" then
return []
else
do
moreInps <- myReadListOrdinary
return (inp:moreInps)
在普通的编程语言中,人们会知道尾递归变体是更好的选择。
然而,经过http://localhost:4200,显然haskell的递归实现与重复使用递归堆栈的实现并不相似。
但是因为在这种情况下,有问题的程序涉及行动和严格的单子,我不确定是否适用相同的推理。事实上,我认为在IO
情况下,尾递归形式确实更好。我不确定如何正确推理这个。
编辑: David Young指出,此处最外面的电话是(>>=)
。即使在这种情况下,这些风格中的一种是否优于另一种?
答案 0 :(得分:2)
这真的不是我写的方式,但是你所做的很清楚。 (顺便说一句,如果你想能够有效地从链中的任何函数插入任意输出,而不使用monad,你可以尝试Data.ByteString.Builder
。)
您的第一个实现非常类似于左侧折叠,而您的第二个非常类似于右侧折叠或地图。 (您可能会尝试实际编写它们!)第二个对I / O有几个优点。处理输入和输出最重要的一点是它可以是交互式的。
你会注意到第一个从外部构建整个列表:为了确定列表的第一个元素是什么,程序需要计算整个结构才能到达最里面的thunk,即{ {1}}。程序首先生成整个数据结构,然后开始处理它。当你减少列表时,这很有用,因为尾递归函数和严格的左折叠是有效的。
对于第二个,最外面的thunk包含列表的头部和尾部,因此你可以抓住尾部,然后调用thunk来生成第二个列表。这可以使用无限列表,它可以生成并返回部分结果。
这是一个人为的例子:一个程序,每行读取一个整数并打印到目前为止的总和。
return l
如果你以交互方式运行,你会得到类似的东西:
main :: IO ()
main = interact( display . compute 0 . parse . lines )
where parse :: [String] -> [Int]
parse [] = []
parse (x:xs) = (read x):(parse xs)
compute :: Int -> [Int] -> [Int]
compute _ [] = []
compute accum (x:xs) = let accum' = accum + x
in accum':(compute accum' xs)
display = unlines . map show
但你也可以使用累积参数来递归地写$ 1
1
$ 2
3
$ 3
6
$ 4
10
:
compute
这是一个人为的例子,但严格的左褶皱是一种常见的模式。但是,如果您使用累积参数编写main :: IO ()
main = interact( display . compute [] . parse . lines )
where parse :: [String] -> [Int]
parse = map read
compute :: [Int] -> [Int] -> [Int]
compute xs [] = reverse xs
compute [] (y:ys) = compute [y] ys
compute (x:xs) (y:ys) = compute (x+y:x:xs) ys
display = unlines . map show
或compute
,则这是您尝试以交互方式运行时所获得的,并且在Unix上命中EOF(parse
,{{1在Windows上)在数字4之后:
control-D
这个左折版本需要先计算整个数据结构才能读取任何数据结构。这无法在无限列表上工作(你何时会达到基本情况?如果你这样做,你甚至会如何反转无限列表?)和一个在退出之前无法响应用户输入的应用程序 - 断路器。
另一方面,尾递归版本在其累积参数中可以是严格的,并且将更有效地运行,尤其是当它没有被立即消耗时。除了参数之外,它不需要保留任何thunk或上下文,甚至可以重用相同的堆栈帧。当您将列表缩减为值,而不是构建急切评估的输出列表时,严格的累积函数(例如control-Z
)是一个很好的选择。 $ 1
$ 2
$ 3
$ 4
1
3
6
10
,Data.List.foldl'
或sum
等函数无法返回任何有用的中间值。它们本身必须首先完成计算,然后返回最终结果。
答案 1 :(得分:2)
FWIW,我会选择现有的monadic组合器,专注于可读性/安心性。使用unfoldM :: Monad m => m (Maybe a) -> m [a]
:
import Control.Monad (liftM, mfilter)
import Control.Monad.Loops (unfoldM)
myReadListTailRecursive :: IO [String]
myReadListTailRecursive = unfoldM go
where
go :: IO (Maybe String)
go = do
line <- getLine
return $ case line of
"" -> Nothing
s -> Just s
使用MonadPlus
Maybe
myReadListTailRecursive :: IO [String]
myReadListTailRecursive = unfoldM (liftM (mfilter (/= "") . Just) getLine)
实例,mfilter :: MonadPlus m => (a -> Bool) -> m a -> m a
:
/* PSEUDOCODE based on PHP */
$selection = $this->getSelection($id);
/* Below code will not work in a strict language */
if (is_array($selection))
show_template($selection);//forwards *array* results to the view script
else
print $selection; //prints the *string* notice message
/* Returns either array or string */
/* return param doubles as a business-payload or error message */
function getSelection($id)
{
$result = $db->getResultsById($id);
if ($result) return $result;//array
else return "No results";//string
}
另一个更通用的选项可能是使用LoopT
。