Так что лучший способ понять это - это сделать. Ниже приведена реализация foldlM
использования foldl
вместо foldr
. Это хорошее упражнение, попробуйте его и попробуйте позже найти решение, которое я бы предложил. В этом примере объясняются все причины, по которым я это сделал, которые могут отличаться от ваших и могут быть предвзятыми, поскольку я уже знал об использовании аккумулятора функций.
Шаг 1 : Давайте попробуем написать foldlM
с точки зренияfoldl
-- this doesn't compile because f returning type is (m b) and not just (b)
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f z0 xs
-- So let substitute f by some undefined f'
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
where f' = undefined
-- cool, but f' should use f somehow in order to get the monadic behaviour
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
where f' b a = f somethingIDontkNow
Здесь вы понимаете, что f'
это чисто, и вам нужно извлечь результат f
для соответствия типа. Единственный способ «извлечь» монадическое значение - использовать >>=
оператор, но такой оператор должен быть перенесен сразу после его использования.
Итак, в заключение: каждый раз, когда вы в конечном итоге, я хотел бы полностью развернуть эту монаду , просто сдаваться. Это не правильный путь
Шаг 2 : Давайте попробуем писать foldlM
в терминах, foldl
но сначала используем []
как складываемые, так как сопоставление с образцом легко (т.е. нам на самом деле не нужно использовать fold
)
-- This is not very hard. It is pretty standard recursion schema. :)
foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 [] = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs
Хорошо, это было легко. Давайте сравним определение с обычным foldl
определением списков
foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 [] = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs
myfoldl :: (b -> a -> b) -> b -> [a] -> b
myfoldl f z0 [] = z0
myfoldl f z0 (x:xs) = foldl f (f z0 x) xs
Круто!! они почти одинаковые. Тривиальный случай примерно такой же. Рекурсивный случай немного по- другому, вы хотели бы написать что - то подобное: foldlM' f (f z0 x) xs
. Но это не компилируется, как в шаге 1, так что вы можете подумать, ОК, я не хочу применять f
, просто чтобы провести такое вычисление и составить его >>=
. Я хотел бы написать что-то более похожее, foldlM' f (f z0 x >>=) xs
если бы это имело смысл ...
Шаг 3 Поймите, что то, что вы хотите накапливать, является композицией функций, а не результатом. ( здесь я, вероятно, предвзятый факт, что я уже знал это, потому что Вы отправили это ).
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' initFunc xs
where initFunc = undefined :: b -> m b
f' = undefined :: (b -> m b) -> a -> (b -> m b) -- This type signature can be deduce because f' should be applied to initFunc and a's from t a.
По типу initFunc
и использованию наших знаний из шага 2 (рекурсивное определение) мы можем вывести это initFunc = return
. Определение f'
может быть завершено, зная, что f'
следует использовать f
и >>=
.
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' return xs z0
-- ^^^^^^
-- |- Initial value
where f' b a = \bvalue -> b bvalue >>= \bresult -> f bresult a -- this is equivalent to (b >=> \result -> f result a) which captures the sequence behaviour of the implementation
-- ^ ^^^^^^ ^^^^^^^
-- | | |- This is the result of previous computation
-- | |- f' should return a function b -> m b. Any time you have to return a function, start writing a lambda
-- |- This b is the accumulated value and has type b -> m b
-- Following the types you can write this with enough practise
Как видите, это не так уж сложно сделать. Это требует практики, но я не профессиональный разработчик Haskell, и я мог бы сделать это сам, это вопрос практики