2014-04-14 12 views
17

Mới với Haskell, và tôi đang cố gắng tìm ra điều Monad này. Nhà điều hành bind monadic - >>= - có một loại chữ ký rất đặc biệt:Tại sao lại sử dụng loại chức năng đặc biệt này trong monads?

(>>=) :: Monad m => m a -> (a -> m b) -> m b 

Để đơn giản hóa, chúng ta hãy thay Maybe cho m:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b 

Tuy nhiên, lưu ý rằng định nghĩa thể đã được viết bằng ba cách khác nhau:

(>>=) :: Maybe a -> (Maybe a -> Maybe b) -> Maybe b 
(>>=) :: Maybe a -> (  a -> Maybe b) -> Maybe b 
(>>=) :: Maybe a -> (  a ->  b) -> Maybe b 

Trong số ba thứ tôi n trung tâm là bất đối xứng nhất. Tuy nhiên, tôi hiểu rằng điều đầu tiên là vô nghĩa nếu chúng ta muốn tránh (những gì LYAH gọi mã soạn sẵn). Tuy nhiên, trong hai người tiếp theo, tôi thích cái cuối cùng. Đối với Maybe, điều này sẽ trông giống như:

Khi điều này được định nghĩa là:

(>>=) :: Maybe a -> (a -> b) -> Maybe b 

instance Monad Maybe where 
    Nothing >>= f = Nothing 
    (Just x) >>= f = return $ f x 

Ở đây, a -> b là một chức năng bình thường. Ngoài ra, tôi không thấy ngay bất kỳ điều gì không an toàn, bởi vì Nothing bắt ngoại lệ trước ứng dụng chức năng, do đó, chức năng a -> b sẽ không được gọi trừ khi thu được Just a.

Vì vậy, có thể có điều gì đó không rõ ràng đối với tôi đã khiến định nghĩa (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b được ưu tiên hơn định nghĩa đơn giản hơn nhiều (>>=) :: Maybe a -> (a -> b) -> Maybe b? Có một số vấn đề vốn có liên quan đến định nghĩa đơn giản hơn (cái tôi nghĩ là a) không?

+0

Có thể bạn không chính xác khi bạn nói rằng định nghĩa liên kết có thể đã được viết khác nhau; lưu ý rằng 'f :: Có thể a -> (Có thể a -> Có thể b) -> Có thể b == lật ($); và 'g :: Có thể a -> (a -> b) -> Có thể b == lật fmap'. Vì vậy, các chức năng khác mà bạn liệt kê đều hữu ích và cả hai đều tồn tại, chúng chỉ sống ở một nơi ngoài 'Monad'. – user2407038

+0

'Có thể a -> (a -> b) -> Có thể b' sẽ không cho phép bạn liên kết một hàm trả về' Có thể b'. Bạn sẽ không thể có được một 'Nothing' nửa chừng thông qua một phép tính. – immibis

+1

Một khả năng khác là 'Có thể -> Có thể (a -> b) -> Có thể b' - về cơ bản là' <*> 'từ' Áp dụng' (loại "ở giữa" Functor và Monad). –

Trả lời

22

Đó là đối xứng nhiều hơn nữa nếu bạn suy nghĩ về chức năng có nguồn gốc sau (từ Control.Monad):

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) 
(f >=> g) x = f x >>= g 

Lý do chức năng này rất có ý nghĩa là nó tuân theo ba phương trình hữu ích:

-- Associativity 
(f >=> g) >=> h = f >=> (g >=> h) 

-- Left identity 
return >=> f = f 

-- Right identity 
f >=> return = f 

Đây là category laws và nếu bạn dịch chúng để sử dụng (>>=) thay vì (>=>), bạn sẽ có được ba luật đơn nguyên:

(m >>= g) >>= h = m >>= \x -> (g x >>= h) 

return x >>= f = f x 

m >>= return = m 

Vì vậy, nó thực sự không phải là (>>=) đó là nhà điều hành thanh lịch nhưng thay vì (>=>) là toán tử đối xứng bạn đang tìm kiếm. Tuy nhiên, lý do chúng tôi thường nghĩ về điều khoản của (>>=) là vì đó là những gì các khoản tiền phạt ký hiệu là do.

+0

Cảm ơn câu trả lời của bạn. Tôi nghĩ rằng tôi kinda-sorta hiểu những gì bạn đang nói. Tôi có lẽ sẽ phải trải qua điều này và giải quyết một loạt các ví dụ trước khi tôi nắm bắt đầy đủ những gì bạn đang nói, nhưng tôi đánh giá cao câu trả lời của bạn. – ssm

+0

Bạn được chào đón! –

8

Chúng ta hãy xem xét một trong những cách sử dụng phổ biến của đơn Maybe: xử lý lỗi. Nói rằng tôi muốn chia hai số một cách an toàn.Tôi có thể viết hàm này:

safeDiv :: Int -> Int -> Maybe Int 
safeDiv _ 0 = Nothing 
safeDiv n d = n `div` d 

Sau đó, với tiêu chuẩn Maybe đơn nguyên, tôi có thể làm một cái gì đó như thế này:

foo :: Int -> Int -> Maybe Int 
foo a b = do 
    c <- safeDiv 1000 b 
    d <- safeDiv a c -- These last two lines could be combined. 
    return d   -- I am not doing so for clarity. 

Lưu ý rằng tại mỗi bước, safeDiv có thể thất bại, nhưng ở cả hai bước, safeDiv mất Int s, không phải Maybe Int s. Nếu >>= có chữ ký này:

(>>=) :: Maybe a -> (a -> b) -> Maybe b 

Bạn có thể soạn các chức năng với nhau, sau đó cung cấp cho nó hoặc là một Nothing hoặc một Just, và một trong hai nó sẽ unwrap Just, đi qua toàn bộ đường ống, và đóng gói lại trong Just , hoặc nó sẽ chỉ vượt qua các Nothing thông qua cơ bản bị ảnh hưởng. Điều đó có thể hữu ích, nhưng nó không phải là một đơn nguyên. Đối với nó được sử dụng bất kỳ, chúng ta phải có khả năng thất bại ở giữa, và đó là những gì chữ ký này cho chúng ta:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b 

Bằng cách này, một cái gì đó với chữ ký bạn nghĩ ra không tồn tại:

flip fmap :: Maybe a -> (a -> b) -> Maybe b 
+3

+1. Thật vậy, 'fmap :: (Functor f) => (a -> b) -> f a -> f b' * là * cực kỳ hữu ích, nhưng ít mạnh hơn' (>> =) 'theo nghĩa được giải thích ở trên. – duplode

+0

+1 Tôi thu thập rằng định nghĩa mà tôi đang xem đã là một Functor? Đúng không? Trong trường hợp đó, Ill phải tìm kiếm sự khác biệt giữa Functors và Monads và tại sao chính xác Monads lại tốt hơn. Tôi không hiểu tất cả mọi thứ ngay lập tức, nhưng bây giờ tôi biết nơi để tìm. Cảm ơn câu trả lời. – ssm

+4

@ssm: Monads không * tốt hơn *, bạn chỉ có thể làm nhiều thứ hơn với chúng (dù sao đi nữa). Tất cả Monads là Functors, do đó, Monad là một Functor với một số công cụ bổ sung. Vì vậy, là áp dụng cho vấn đề đó. Trong thực tế, tất cả Monads là các ứng dụng và tất cả các ứng dụng là Functors. –

2

Vấn đề với chữ ký loại thay thế cho (>>=) là vấn đề chỉ hoạt động cho đơn nguyên Có lẽ, nếu bạn thử với một đơn nguyên khác (ví dụ: Danh sách đơn nguyên), bạn sẽ thấy nó bị hỏng ở loại b cho trường hợp chung. Chữ ký bạn cung cấp không mô tả một ràng buộc đơn thuần và các luật đơn nguyên không thể không giữ nguyên định nghĩa đó.

import Prelude hiding (Monad, return) 

-- assume monad was defined like this 
class Monad m where 
    (>>=) :: m a -> (a -> b) -> m b 
    return :: a -> m a 

instance Monad Maybe where 
    Nothing >>= f = Nothing 
    (Just x) >>= f = return $ f x 

instance Monad [] where 
    m >>= f = concat (map f m) 
    return x = [x] 

Thất bại với lỗi loại:

Couldn't match type `b' with `[b]' 
     `b' is a rigid type variable bound by 
      the type signature for >>= :: [a] -> (a -> b) -> [b] 
      at monadfail.hs:12:3 
    Expected type: a -> [b] 
     Actual type: a -> b 
    In the first argument of `map', namely `f' 
    In the first argument of `concat', namely `(map f m)' 
    In the expression: concat (map f m) 
+1

Cách đơn giản 'm >> = f = map f m'? – ssm

+1

@sm: Đó là một minh chứng tốt rằng chữ ký loại '>> =' mà bạn đang đề xuất sẽ tương đương chính xác với sức mạnh của 'Functor' (' Monad' vì nó tồn tại mạnh hơn 'Functor'). –

+0

Đó chính xác là định nghĩa Functor cho fmap, không phải là một đơn nguyên. –

3

Chức năng phức tạp hơn với a -> Maybe b là một trong những chung chung hơn và hữu ích hơn và có thể được sử dụng để thực hiện một đơn giản. Điều đó không hoạt động theo cách khác.

Bạn có thể xây dựng một hàm a -> Maybe b từ một hàm f :: a -> b:

f' :: a -> Maybe b 
f' x = Just (f x) 

Hoặc, về mặt return (đó là Just cho Maybe):

f' = return . f 

Một cách khác xung quanh không nhất thiết phải khả thi. Nếu bạn có hàm g :: a -> Maybe b và muốn sử dụng hàm này với liên kết "đơn giản", bạn sẽ phải chuyển đổi nó thành hàm a -> b trước tiên. Nhưng điều này thường không hoạt động, bởi vì g có thể trả lại Nothing trong đó chức năng a -> b cần trả lại giá trị b.

Vì vậy, nói chung, liên kết "đơn giản" có thể được triển khai theo dạng "phức tạp", nhưng không phải theo cách khác. Ngoài ra, các ràng buộc phức tạp thường hữu ích và không có nó sẽ làm cho nhiều điều không thể. Vì vậy, bằng cách sử dụng các monads ràng buộc chung chung hơn được áp dụng cho nhiều tình huống.

1

Điều tạo nên một đơn nguyên là cách 'tham gia' hoạt động.Nhớ lại rằng tham gia có loại:

join :: m (m a) -> m a 

Điều gì 'tham gia' là "diễn giải" một hành động đơn lẻ trả về một hành động đơn lẻ về hành động đơn thuần. Vì vậy, bạn có thể nghĩ rằng nó lột đi một lớp của monad (hoặc tốt hơn, kéo các công cụ trong lớp bên trong ra vào lớp bên ngoài). Điều này có nghĩa là 'm'tạo thành một "ngăn xếp", theo nghĩa là "ngăn xếp cuộc gọi". Mỗi 'm' đại diện cho một bối cảnh, và 'tham gia' cho phép chúng ta tham gia các bối cảnh với nhau, theo thứ tự.

Vì vậy, điều này có liên quan gì đến việc ràng buộc? Nhớ lại:

(>>=) :: m a -> (a -> m b) -> m b 

Và bây giờ xem xét rằng cho f :: a -> mb, và ma :: ma:

fmap f ma :: m (m b) 

Đó là, kết quả của việc áp dụng f trực tiếp đến một trong ma là một (m (mb)). Chúng tôi có thể áp dụng tham gia này, để có được một m b. Tóm lại,

ma >>= f = join (fmap f ma) 
Các vấn đề liên quan