Тож найкращий спосіб зрозуміти це, зробивши це. Нижче є реалізація 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, і я міг би це зробити сам, це питання практики