【发布时间】:2018-06-08 14:27:31
【问题描述】:
foldr 有一种技术我见过几次。它涉及使用函数代替foldr 中的累加器。我想知道什么时候需要这样做,而不是使用只是一个常规值的累加器。
大多数人以前在使用foldr 定义foldl 时都见过这种技术:
myFoldl :: forall a b. (b -> a -> b) -> b -> [a] -> b
myFoldl accum nil as = foldr f id as nil
where
f :: a -> (b -> b) -> b -> b
f a continuation b = continuation $ accum b a
这里,组合函数f的类型不仅仅是像正常的a -> b -> b,而是a -> (b -> b) -> b -> b。它不仅需要a 和b,还需要一个延续(b -> b),我们需要将b 传递给以得到最终的b。
我最近在Parallel and Concurrent Programming in Haskell 一书中看到了一个使用这个技巧的例子。 Here 是使用此技巧的示例源代码的链接。 Here 是本书解释这个例子的章节的链接。
我冒昧地将源代码简化为一个类似(但更短)的示例。下面是一个函数,它接受一个字符串列表,打印出每个字符串的长度是否大于 5,然后仅打印长度大于 5 的字符串的完整列表:
import Text.Printf
stringsOver5 :: [String] -> IO ()
stringsOver5 strings = foldr f (print . reverse) strings []
where
f :: String -> ([String] -> IO ()) -> [String] -> IO ()
f str continuation strs = do
let isGreaterThan5 = length str > 5
printf "Working on \"%s\", greater than 5? %s\n" str (show isGreaterThan5)
if isGreaterThan5
then continuation $ str : strs
else continuation strs
这是一个在 GHCi 中使用它的示例:
> stringsOver5 ["subdirectory", "bye", "cat", "function"]
Working on "subdirectory", greater than 5? True
Working on "bye", greater than 5? False
Working on "cat", greater than 5? False
Working on "function", greater than 5? True
["subdirectory","function"]
就像在myFoldl 示例中一样,您可以看到组合函数f 使用了相同的技巧。
但是,我想到这个stringsOver5 函数可能不用这个技巧就可以编写:
stringsOver5PlainFoldr :: [String] -> IO ()
stringsOver5PlainFoldr strings = foldr f (pure []) strings >>= print
where
f :: String -> IO [String] -> IO [String]
f str ioStrs = do
let isGreaterThan5 = length str > 5
printf "Working on \"%s\", greater than 5? %s\n" str (show isGreaterThan5)
if isGreaterThan5
then fmap (str :) ioStrs
else ioStrs
(虽然也许你可以提出IO [String]is a continuation的论点?)
对此我有两个问题:
- 是否绝对有必要使用这种将延续传递给
foldr的技巧,而不是使用带有正常值的foldr作为累加器?有没有一个绝对不能使用foldr和正常值编写的函数的例子? (当然,除了foldl和类似的功能。) - 我什么时候想在我自己的代码中使用这个技巧?有没有使用这个技巧可以显着简化的函数示例?
- 在使用此技巧时是否需要考虑任何类型的性能注意事项? (或者,当不使用这个技巧的时候?)
【问题讨论】:
标签: haskell io fold continuation-passing