2016-08-02 19 views
15

Trong nhiều trường hợp, nó không rõ ràng với tôi những gì là để đạt được bằng cách kết hợp hai monads với một biến áp thay vì sử dụng hai monads riêng biệt. Rõ ràng, bằng cách sử dụng hai monads riêng biệt là một rắc rối và có thể liên quan đến ký hiệu bên trong ký hiệu, nhưng có trường hợp mà nó chỉ là không đủ biểu cảm?Tại sao các biến thế đơn nguyên lại khác biệt với các đơn vị xếp chồng?

Một trường hợp có vẻ là StateT trong Danh sách: kết hợp các đơn vị không giúp bạn đúng loại và nếu bạn có đúng loại thông qua một chồng đơn lẻ như Bar (trong đó Bar a = (Reader r (List) Writer w (Identity a))), nó không làm điều đúng.

Nhưng tôi muốn có một sự hiểu biết chung và kỹ thuật hơn về chính xác những gì biến áp đơn nguyên được đưa vào bảng, khi chúng là và aren ' t cần thiết, và tại sao

Để làm cho câu hỏi này một chút tập trung hơn.

  1. Một ví dụ thực tế của một đơn vị không có biến áp tương ứng (điều này sẽ giúp minh họa những gì máy biến áp có thể làm điều đó chỉ cần xếp chồng monads không thể).
  2. Are StateT và ContT máy biến áp duy nhất mà cung cấp cho một loại không tương đương với các thành phần của chúng với m, cho một đơn nguyên m nằm bên dưới (bất kể là thứ tự mà chúng tôi sáng tác.)

(Tôi không quan tâm đến các chi tiết thực hiện cụ thể liên quan đến các lựa chọn khác nhau của thư viện, mà là câu hỏi chung (và có lẽ là Haskell) về những biến đổi/biến đổi đơn điệu được thêm vào như là một cách thay thế để kết hợp các hiệu ứng bằng cách xếp chồng các nhà xây dựng kiểu đơn lẻ.)

(Để cung cấp cho một bối cảnh nhỏ, tôi là một nhà ngôn ngữ học đang làm một dự án để làm giàu ngữ pháp Montague - chỉ cần gõ tính toán lambda cho compos ing ý nghĩa từ thành câu - với một chồng biến áp monad. Nó sẽ thực sự hữu ích để hiểu xem máy biến áp đang thực sự làm bất cứ điều gì hữu ích cho tôi.)

Cảm ơn,

Reuben

+1

Ý bạn là gì khi "sử dụng hai monads riêng biệt"? Bạn có thể đưa ra một ví dụ không? – ErikR

+0

Bản sao có thể có của [mtl, máy biến áp, monads-fd, monadLib và nghịch lý lựa chọn] (http://stackoverflow.com/questions/2769487/mtl-transformers-monads-fd-monadlib-and-the-paradox- của sự lựa chọn) –

+0

Tôi đã bỏ phiếu để đóng bởi vì nó thực sự có âm thanh như bạn đang yêu cầu cho sự khác biệt giữa các thư viện biến áp monad khác nhau, và câu hỏi đó đã có một câu trả lời xuất sắc. Nếu bạn nghĩ rằng câu hỏi của bạn là khác nhau, có lẽ bạn có thể xây dựng. –

Trả lời

17

Để trả lời bạn câu hỏi về sự khác biệt giữa Writer w (Maybe a) vs MaybeT (Writer w) a, chúng ta hãy bắt đầu bằng cách nhìn vào các định nghĩa:

newtype WriterT w m a = WriterT { runWriterT :: m (a, w) } 
type Writer w = WriterT w Identity 

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } 

Sử dụng ~~ để có nghĩa là "có cấu trúc tương tự", chúng tôi có:

Writer w (Maybe a) == WriterT w Identity (Maybe a) 
        ~~ Identity (Maybe a, w) 
        ~~ (Maybe a, w) 

MaybeT (Writer w) a ~~ (Writer w) (Maybe a) 
        == Writer w (Maybe a) 
        ... same derivation as above ... 
        ~~ (Maybe a, w) 

Vì vậy, trong một cảm giác bạn là chính xác - cấu trúc cả Writer w (Maybe a)MaybeT (Writer w) a đều giống nhau - cả về cơ bản chỉ là một cặp một lẽ giá trị và một w.

Sự khác biệt là cách chúng tôi coi chúng là giá trị đơn nguyên. Các chức năng lớp return>>= làm những việc rất khác nhau tùy thuộc vào trên đó đơn nguyên chúng là một phần của.

Hãy xem xét cặp (Just 3, []::[String]). Sử dụng các hiệp hội chúng tôi đã thu được trên đây là cách cặp đó sẽ được thể hiện trong cả hai monads:

three_W :: Writer String (Maybe Int) 
three_W = return (Just 3) 

three_M :: MaybeT (Writer String) Int 
three_M = return 3 

Và đây là cách chúng tôi sẽ xây dựng một cặp (Nothing, []):

nutin_W :: Writer String (Maybe Int) 
nutin_W = return Nothing 

nutin_M :: MaybeT (Writer String) Int 
nutin_M = MaybeT (return Nothing) -- could also use mzero 

Bây giờ xem xét chức năng này trên cặp:

add1 :: (Maybe Int, String) -> (Maybe Int, String) 
add1 (Nothing, w) = (Nothing w) 
add1 (Just x, w) = (Just (x+1), w) 

và chúng ta hãy xem cách chúng tôi sẽ thực hiện nó trong hai monads khác nhau:

add1_W :: Writer String (Maybe Int) -> Writer String (Maybe Int) 
add1_W e = do x <- e 
      case x of 
       Nothing -> return Nothing 
       Just y -> return (Just (y+1)) 

add1_M :: MaybeT (Writer String) Int -> MaybeT (Writer String) Int 
add1_M e = do x <- e; return (e+1) 
    -- also could use: fmap (+1) e 

Nói chung, bạn sẽ thấy rằng mã trong đơn vị ProbT ngắn gọn hơn.

Hơn nữa, ngữ nghĩa hai monads rất khác nhau ...

MaybeT (Writer w) a là một Writer hành động đó có thể thất bại, và thất bại là tự động xử lý cho bạn. Writer w (Maybe a) chỉ là tác vụ Writer trả về Có thể. Không có gì đặc biệt xảy ra nếu điều đó Có thể giá trị hóa ra là Không có gì. Điều này được minh họa trong hàm add1_W trong đó chúng tôi phải thực hiện phân tích trường hợp trên x.

Một lý do khác để thích cách tiếp cận MaybeT là chúng tôi có thể viết mã là đặc điểm chung trên bất kỳ ngăn xếp đơn nguyên nào. Ví dụ: chức năng:

square x = do tell ("computing the square of " ++ show x) 
       return (x*x) 

có thể được sử dụng không thay đổi trong bất kỳ ngăn đơn nguyên nào có chuỗi Writer, ví dụ::

WriterT String IO 
ReaderT (WriterT String Maybe) 
MaybeT (Writer String) 
StateT (WriterT String (ReaderT Char IO)) 
... 

Nhưng giá trị trở lại của square không gõ kiểm tra chống Writer String (Maybe Int)square không trả lại một Maybe.

Khi bạn mã số Writer String (Maybe Int), bạn mã rõ ràng tiết lộ cấu trúc đơn nguyên làm cho nó ít chung chung hơn. Định nghĩa này của add1_W:

add1_W e = do x <- e 
       return $ do 
       y <- x 
       return $ y + 1 

chỉ hoạt động trong một hai lớp đơn nguyên đống trong khi một chức năng như square công trình trong một khung cảnh tổng quát hơn nhiều.

+0

Cảm ơn, điều này rất hữu ích. Nhưng tôi nghĩ câu hỏi của tôi vẫn đứng vững.Giả sử cho add1_W, bạn đã viết như sau: add1_W e = làm x <- e return $ làm y <- x return $ y + 1 Đây là vẫn còn nhiều rắc rối hơn add1_M, nhưng không yêu cầu bất kỳ báo cáo trường hợp nào, và sử dụng cả hai có thể và nhà văn monads. – Reuben

+1

Đã cập nhật câu trả lời. – ErikR

+0

Một sửa đổi nhỏ: trong add1_M Tôi tin rằng bạn có nghĩa là 'return (x + 1)'. – Ryan

6

một ví dụ thực tế của một đơn nguyên không có biến tương ứng là gì (sẽ này giúp minh họa những gì máy biến áp có thể làm điều đó chỉ cần xếp chồng monads không thể).

IOST là các ví dụ kinh điển tại đây.

StateT và ContT các máy biến áp duy nhất cung cấp loại không tương đương với thành phần của chúng với m, đối với đơn nguyên cơ bản m (bất kể thứ tự chúng được tạo thành.)

Không, ListT m a là không (đẳng cấu với) [m a]:

newtype ListT m a = 
    ListT { unListT :: m (Maybe (a, ListT m a)) } 
+1

Có thể thú vị để lưu ý rằng nó vẫn gần như là bố cục, bạn chỉ cần di chuyển bố cục theo điểm cố định: 'List a = Fix (1 + a * x)' và 'ListT ma = Fix (m ∘ (1 + a * x)) ' – Cactus

Các vấn đề liên quan