Есть техника, которую я видел несколько раз с 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
.
I в последнее время.видел пример использования этого трюка в книге Параллельное и параллельное программирование на Haskell . Здесь - ссылка на исходный код примера, использующего этот трюк. Здесь - ссылка на главу книги, объясняющую этот пример.
Я позволил себе упростить упрощение исходного кода до аналогичного (но более короткого) примера.Ниже приведена функция, которая принимает список строк, распечатывает, превышает ли длина каждой строки пять, а затем печатает полный список только строк, длина которых превышает пять:
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]
является продолжением ?)
У меня есть два вопроса по этому поводу:
- Совершенно необходимо ли использовать этот прием для передачи продолжения в
foldr
вместо использования foldr
снормальное значение как аккумулятор?Есть ли пример функции, которая абсолютно не может быть написана с использованием foldr
с нормальным значением?(Помимо foldl
и подобных функций, конечно.) - Когда я захочу использовать этот трюк в своем собственном коде?Есть ли какой-нибудь пример функции, которую можно значительно упростить с помощью этого трюка?
- Есть ли какие-либо соображения производительности, которые следует учитывать при использовании этого трюка?(Или, ну, когда не использует этот трюк?)