Những loại kiến ​​thức hoặc đào tạo nào là cần thiết để ai đó viết ra định nghĩa về FoldlM như thế này? [đóng cửa]


9

Gần đây, tôi đang cố gắng sử dụng Haskell trong một số hệ thống sản xuất trường hợp thực tế của mình. Hệ thống loại Haskell thực sự cung cấp cho tôi sự giúp đỡ tuyệt vời. Ví dụ, khi tôi nhận ra rằng tôi cần một số chức năng của loại

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

Có những chức năng thực sự như foldM, foldlMfoldrM.

Tuy nhiên, điều thực sự gây sốc cho tôi là định nghĩa của các chức năng này, chẳng hạn như:

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

vì vậy chức năng f'phải là loại:

f' :: a -> b -> b

theo yêu cầu foldr, sau đó bphải là loại *-> m *, vì vậy toàn bộ định nghĩa foldlMcó thể có ý nghĩa.

Một ví dụ khác bao gồm các định nghĩa về 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)

Tôi đã thử một số giải pháp của riêng mình trước khi nhìn trộm vào mã nguồn. Nhưng khoảng cách quá lớn đến nỗi tôi không nghĩ mình có thể đưa ra giải pháp này, bất kể tôi sẽ viết bao nhiêu dòng mã trong tương lai.

Vì vậy, câu hỏi của tôi là, loại kiến ​​thức hoặc ngành toán học cụ thể nào là cần thiết cho một người nào đó để lý luận ở mức độ trừu tượng cao như vậy.

Tôi biết lý thuyết Danh mục có thể cung cấp một số trợ giúp và tôi đã theo dõi bài giảng tuyệt vời này trong một thời gian dài và vẫn đang tiếp tục thực hiện nó.


13
Haskell là một ngôn ngữ. Nó có nhiều từ, và hầu hết những từ đó có thể được sử dụng theo nhiều cách khác nhau. Khi bạn đang học một ngôn ngữ mới, trong nhiều năm, nhiều câu và thành ngữ không có ý nghĩa. Nhưng bạn càng sử dụng nó, bạn càng thấy các mẫu quen thuộc và những thứ bạn từng nghĩ là đáng sợ và tiên tiến đến khá tự nhiên. Thư giãn.
luqui

Câu trả lời:


3

Nói chung, logic, vv, tôi sẽ tưởng tượng. Nhưng bạn cũng có thể học nó bằng cách làm nó. :) Với thời gian bạn nhận thấy một số mẫu, chọn một số thủ thuật.

Như thế này foldrvới một điều tranh luận thêm. Một số người coi nó như là gấp thành các chức năng để chúng có thể được kết hợp thông qua .id( đôi khi thực sự <=<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)
-}

Một số người thấy dễ hiểu hơn về mặt đơn giản, cú pháp, như

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

do đó, khi gkhông nghiêm ngặt trong đối số thứ hai của nó, scó thể đóng vai trò là trạng thái được truyền từ bên trái, mặc dù chúng ta đang gấp bên phải, như một ví dụ.


1
Cảm ơn bạn rất nhiều, tôi đã cố gắng tìm hiểu xem định nghĩa này có phải là duy nhất hay không và không mong đợi việc sử dụng thành phần Kleisli ở đây. Câu trả lời này thực sự giải quyết nghi ngờ của tôi.
Theodora

không có gì. :)
Will Ness

4

Vì vậy, cách tốt nhất để hiểu nó là bằng cách làm điều đó. Dưới đây có một triển khai foldlMsử dụng foldlthay vì foldr. Đó là một bài tập hay, hãy thử và đến sau với giải pháp tôi đề xuất. Ví dụ giải thích tất cả lý do tôi đã thực hiện để đạt được nó, có thể khác với lý do của bạn và có thể là sai lệch vì tôi đã biết về việc sử dụng bộ tích lũy hàm.

Bước 1 : Chúng ta hãy thử viết foldlMtheofoldl

-- 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 

Ở đây bạn nhận ra rằng đó f'là thuần túy và bạn cần trích xuất kết quả fđể nhập kết quả khớp. Cách duy nhất để "giải nén" một giá trị đơn trị là với >>=toán tử, nhưng toán tử đó cần được bọc ngay sau khi sử dụng.

Vì vậy, như một kết luận: Mỗi khi bạn kết thúc với việc tôi muốn hoàn toàn mở khóa đơn nguyên này , hãy từ bỏ. Không đúng cách

Bước 2 : Chúng ta hãy thử viết foldlMtheo cách foldlnhưng trước tiên sử dụng []như có thể gập lại, vì nó rất dễ để khớp mẫu (nghĩa là chúng ta không thực sự cần sử dụng 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

Ok, thật dễ dàng. Hãy so sánh định nghĩa với foldlđịnh nghĩa thông thường cho danh sách

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

Mát mẻ!! chúng khá giống nhau Các trường hợp tầm thường là về điều tương tự chính xác. Trường hợp đệ quy hơi khác một chút, bạn muốn viết một cái gì đó giống như : foldlM' f (f z0 x) xs. Nhưng không được biên dịch như trong bước 1, vì vậy bạn có thể nghĩ OK, tôi không muốn áp dụng f, chỉ để giữ một tính toán như vậy và soạn nó với >>=. Tôi muốn viết một cái gì đó giống như foldlM' f (f z0 x >>=) xs nếu nó có ý nghĩa ...

Bước 3 Nhận ra rằng những gì bạn muốn tích lũy là một thành phần chức năng và không phải là kết quả. ( ở đây tôi có lẽ thiên vị bởi thực tế là tôi đã biết nó vì bạn đã đăng nó ).

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. 

Theo loại initFuncvà sử dụng kiến ​​thức của chúng tôi từ bước 2 (định nghĩa đệ quy), chúng tôi có thể suy ra điều đó initFunc = return. Định nghĩa của f'có thể được hoàn thành biết rằng f'nên sử dụng 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

Như bạn có thể thấy, nó không quá khó để làm điều đó. Nó cần thực hành, nhưng tôi không phải là nhà phát triển haskell chuyên nghiệp và tôi có thể tự làm được, đó là vấn đề thực hành


1
Tôi không thực sự thấy những gì làm cho một nếp gấp bên trái dễ hiểu hơn một nếp gấp bên phải ở đây. Nếp gấp bên phải có nhiều khả năng tạo ra kết quả hữu ích cho các cấu trúc vô hạn và hiệu quả cho các Monadtrường hợp điển hình .
dfeuer

@dfeuer Quan điểm của việc này không phải là để đưa ra một ví dụ dễ dàng hơn, mà là đề xuất một bài tập phù hợp cho OP và đưa ra một lập luận hợp lý của giải pháp, cố gắng chứng minh rằng không cần thiết phải là một người bán siêu chủ để có được giải pháp như vậy Những đánh giá về hiệu quả không được tính
lsmor

3

Bạn không cần bất kỳ kiến ​​thức cụ thể nào trong toán học để viết một hàm như thế nào foldM. Tôi đã sử dụng Haskell trong sản xuất được 4 năm và tôi cũng đang vật lộn để hiểu định nghĩa này foldM. Nhưng điều đó chủ yếu là vì nó được viết kém. Xin vui lòng, đừng coi đó là lỗi cá nhân nếu bạn không thể hiểu một số mã tối nghĩa. Đây là một phiên bản dễ đọc hơn củafoldlM

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

Chức năng này vẫn chưa phải là dễ nhất. Chủ yếu là vì nó có cách sử dụng không điển hình trong foldrđó bộ tích lũy trung gian là một hàm. Nhưng bạn có thể thấy một vài mays làm cho định nghĩa như vậy dễ đọc hơn:

  1. Nhận xét về đối số chức năng.
  2. Tên đối số tốt hơn (vẫn ngắn và thành ngữ, nhưng ít nhất chúng dễ đọc hơn).
  3. Chữ ký loại rõ ràng của hàm bên trong where(để bạn biết hình dạng của các đối số).

Sau khi thấy chức năng như vậy, bây giờ bạn có thể thực hiện kỹ thuật lý luận tương đương để mở rộng định nghĩa từng bước và xem cách nó hoạt động. Khả năng đến với các chức năng như vậy đi kèm với kinh nghiệm. Tôi không có kỹ năng toán học mạnh mẽ và chức năng này không phải là chức năng Haskell điển hình. Nhưng bạn càng thực hành nhiều thì càng tốt :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.