ดังนั้นวิธีที่ดีที่สุดที่จะเข้าใจก็คือทำมัน ด้านล่างมีการดำเนินการfoldlM
โดยใช้แทนfoldl
foldr
มันเป็นการออกกำลังกายที่ดีลองและมาหาคำตอบที่ฉันจะแนะนำในภายหลัง ตัวอย่างอธิบายเหตุผลทั้งหมดที่ฉันได้ทำเพื่อให้บรรลุซึ่งอาจแตกต่างจากของคุณและอาจมีอคติเพราะฉันรู้แล้วเกี่ยวกับการใช้ฟังก์ชันตัวสะสม
ขั้นตอนที่ 1 : ลอง Let 's ไปเขียน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
ประเภทการจับคู่ วิธีเดียวในการ 'แยก' ค่า monadic คือมี>>=
ตัวดำเนินการ แต่ตัวดำเนินการดังกล่าวจะต้องถูกตัดคำทันทีหลังจากใช้
ดังนั้นโดยสรุป: ทุกครั้งที่คุณจบลงด้วยฉันอยากจะแกะพระนี้ออกมาอย่างเต็มที่เพียงแค่ยอมแพ้ ไม่ใช่วิธีที่ถูกต้อง
ขั้นตอนที่ 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 (คำนิยาม recursive) 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
อย่างที่คุณเห็นมันไม่ยากเลยที่จะทำ มันต้องการการฝึกฝน แต่ฉันไม่ใช่นักพัฒนามืออาชีพที่เก่งและฉันสามารถทำได้ด้วยตัวเองมันเป็นเรื่องของการฝึกฝน