จำเป็นต้องมีความรู้หรือการฝึกอบรมประเภทใดสำหรับคนที่จะเขียนคำจำกัดความของ foldlM แบบนี้ [ปิด]


9

เมื่อเร็ว ๆ นี้ฉันพยายามใช้ Haskell ในระบบการผลิตกรณีจริงของฉัน ระบบประเภท Haskell ให้ความช่วยเหลือฉันอย่างมาก ตัวอย่างเช่นเมื่อฉันรู้ว่าฉันต้องการฟังก์ชั่นบางประเภท

f :: (Foldable t, Monad m) => ( a-> b -> m b) -> b -> t a -> m b

มีจริงการทำงานเช่นfoldM, และfoldlMfoldrM

อย่างไรก็ตามสิ่งที่น่าตกใจจริง ๆ คือนิยามของฟังก์ชั่นเหล่านี้เช่น:

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldr f' return xs z0
  where f' x k z = f z x >>= k

ฟังก์ชันf'ต้องเป็นประเภท:

f' :: a -> b -> b

ตามที่กำหนดโดยfoldrนั้นbจะต้องเป็นของชนิด*-> m *ดังนั้นคำจำกัดความทั้งหมดของfoldlMสามารถทำให้รู้สึก

อีกตัวอย่างรวมถึงคำจำกัดความของliftA2และ<*>

(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)

ฉันลองวิธีแก้ปัญหาของตัวเองก่อนที่จะแอบดูซอร์สโค้ด แต่ช่องว่างมีขนาดใหญ่มากจนฉันไม่คิดว่าฉันจะสามารถหาวิธีแก้ปัญหานี้ได้ไม่ว่าในอนาคตฉันจะเขียนโค้ดหลายบรรทัด

ดังนั้นคำถามของฉันคือความรู้ชนิดใดหรือสาขาคณิตศาสตร์ที่เฉพาะเจาะจงเป็นสิ่งจำเป็นสำหรับบางคนที่ให้เหตุผลในระดับที่เป็นนามธรรม

ฉันรู้ว่าหมวดหมู่ทฤษฎีอาจให้ความช่วยเหลือและฉันได้ติดตามการบรรยายที่ยอดเยี่ยมนี้มาเป็นเวลานานและยังคงทำงานต่อไป


13
Haskell เป็นภาษา มันมีหลายคำและส่วนใหญ่ของคำเหล่านั้นสามารถใช้ในหลากหลายวิธี เมื่อคุณเรียนภาษาใหม่ประโยคและสำนวนหลายปีที่ไม่เข้าท่า แต่ยิ่งคุณใช้มันมากเท่าไหร่คุณก็ยิ่งเห็นรูปแบบที่คุ้นเคยและสิ่งที่คุณเคยคิดว่าน่ากลัวและก้าวหน้ามาโดยธรรมชาติ ผ่อนคลาย.
luqui

คำตอบ:


3

โดยทั่วไปแล้วตรรกะ ฯลฯ ฉันจะจินตนาการ แต่คุณยังสามารถเรียนรู้ได้ด้วยการทำมัน :) เมื่อถึงเวลาที่คุณสังเกตเห็นลวดลายให้หยิบลูกเล่น

เช่นนี้foldrกับสิ่งที่โต้แย้งเป็นพิเศษ บางคนมองว่ามันเป็นการพับลงในฟังก์ชั่นเพื่อให้สามารถรวมกัน.และid(ซึ่งบางครั้งก็เป็นจริง<=<และreturn)

foldr g z xs  =  foldr ($) z . map g $ xs
              =  foldr (.) id (map g xs) z
         ~/=  foldr (<=<) return (map g xs) z
{-
  (\f -> f . f) :: (a -> a) -> (a -> a)

  (\f -> f <=< f) :: Monad m => (a -> m a) -> (a -> m a)
                            (still just a type, not a kind)
-}

บางคนพบว่าเข้าใจง่ายขึ้นในแง่ง่ายกว่า

foldr g z [a,b,c,...,n] s =
     g a (foldr g z [b,c,...,n]) s

ดังนั้นเมื่อgไม่เข้มงวดในการโต้แย้งที่สองsสามารถทำหน้าที่เป็นรัฐที่ถูกส่งผ่านจากด้านซ้ายแม้ว่าเราจะพับทางด้านขวาเป็นตัวอย่างหนึ่ง


1
ขอบคุณมากฉันพยายามคิดออกว่าคำจำกัดความนี้มีลักษณะเฉพาะหรือไม่คาดหวังว่าจะใช้การจัดองค์ประกอบของ Kleisli ที่นี่ คำตอบนี้จริงๆแก้ข้อสงสัยของฉัน
Theodora

ไม่เป็นไร :)
Will Ness

4

ดังนั้นวิธีที่ดีที่สุดที่จะเข้าใจก็คือทำมัน ด้านล่างมีการดำเนินการ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

อย่างที่คุณเห็นมันไม่ยากเลยที่จะทำ มันต้องการการฝึกฝน แต่ฉันไม่ใช่นักพัฒนามืออาชีพที่เก่งและฉันสามารถทำได้ด้วยตัวเองมันเป็นเรื่องของการฝึกฝน


1
ฉันไม่เห็นว่าสิ่งที่ทำให้พับครึ่งด้านซ้ายนั้นง่ายต่อการเข้าใจมากกว่ารอยพับด้านขวาที่นี่ การพับด้านขวานั้นมีแนวโน้มที่จะสร้างผลลัพธ์ที่เป็นประโยชน์สำหรับโครงสร้างที่ไม่มีที่สิ้นสุดและมีประสิทธิภาพสำหรับMonadกรณีทั่วไป
dfeuer

@dfeuer ประเด็นนี้ไม่ใช่การแสดงตัวอย่างที่ง่ายขึ้น แต่เสนอการฝึกที่เหมาะสมสำหรับ OP และแสดงเหตุผลของการแก้ปัญหาโดยพยายามพิสูจน์ว่าไม่จำเป็นต้องเป็นซูเปอร์มาสเตอร์เพื่อที่จะได้รับ วิธีการแก้ปัญหาดังกล่าว การแยกส่วนเกี่ยวกับประสิทธิภาพไม่ได้รับการพิจารณา
lsmor

3

คุณไม่จำเป็นต้องมีความรู้เฉพาะทางในวิชาคณิตศาสตร์เพื่อเขียนฟังก์ชั่นเช่นfoldMนั้น ฉันใช้ Haskell ในการผลิตเป็นเวลา 4 foldMปีแล้วและฉันยังดิ้นรนกับการทำความเข้าใจความหมายของการนี้ แต่ส่วนใหญ่เป็นเพราะมันเขียนไม่ดี โปรดอย่าถือเป็นความผิดพลาดส่วนบุคคลหากคุณไม่สามารถเข้าใจรหัสคลุมเครือ นี่คือรุ่นที่อ่านได้มากขึ้นของfoldlM

foldlM
    :: forall t m a b .
       (Foldable t, Monad m)
    => (b -> a -> m b)  -- ^ Monadic action
    -> b                -- ^ Starting accumulator
    -> t a              -- ^ List of values
    -> m b              -- ^ Computation result inside a monad
foldlM f z xs = (foldr step pure xs) z
  where
    step :: a -> (b -> m b) -> b -> m b
    step cur next acc = do
        result <- f acc cur
        next result

ฟังก์ชั่นนี้ยังไม่ง่ายที่สุด ส่วนใหญ่เป็นเพราะมันมีการใช้งานที่ไม่ปกติของfoldrที่สะสมกลางเป็นฟังก์ชั่น แต่คุณสามารถดู mays สองสามดวงที่ทำให้คำจำกัดความดังกล่าวอ่านง่ายขึ้น:

  1. ความคิดเห็นเกี่ยวกับอาร์กิวเมนต์ของฟังก์ชัน
  2. ชื่ออาร์กิวเมนต์ที่ดีกว่า (ยังสั้นและเป็นสำนวน แต่อย่างน้อยก็สามารถอ่านได้มากกว่า)
  3. ลายเซ็นชนิดที่ชัดเจนของฟังก์ชันภายในwhere(เพื่อให้คุณทราบถึงรูปร่างของอาร์กิวเมนต์)

หลังจากเห็นฟังก์ชั่นดังกล่าวคุณสามารถใช้เทคนิคการให้เหตุผลเชิงเหตุผลเพื่อขยายคำจำกัดความทีละขั้นตอนและดูว่ามันทำงานอย่างไร ความสามารถที่จะเกิดขึ้นกับฟังก์ชั่นดังกล่าวมาพร้อมกับประสบการณ์ ฉันไม่มีทักษะทางคณิตศาสตร์ที่แข็งแกร่งและฟังก์ชั่นนี้ไม่ใช่ฟังก์ชั่นทั่วไปของ Haskell แต่ยิ่งคุณฝึกฝนมากเท่าไหร่ก็ยิ่งได้รับมากขึ้นเท่านั้น :)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.