2012-03-09 24 views
17

Tôi đã trở nên khá quan tâm đến cách tính toán được mô hình hóa trong Haskell. Một số tài nguyên đã mô tả các monads là "tính toán tổng hợp" và các mũi tên là "các quan điểm trừu tượng về tính toán". Tôi chưa bao giờ nhìn thấy monoids, functors hoặc functors ứng dụng được mô tả theo cách này. Có vẻ như họ thiếu cấu trúc cần thiết.Xây dựng tính toán (Monads, Mũi tên, vv)

Tôi thấy rằng ý tưởng thú vị và tự hỏi nếu có bất kỳ cấu trúc nào khác làm điều gì đó tương tự. Nếu vậy, một số tài nguyên mà tôi có thể sử dụng để làm quen với họ là gì? Có bất kỳ gói nào trên Hackage có thể có ích không?

Lưu ý: Câu hỏi này cũng tương tự như Monads vs. Arrowshttps://stackoverflow.com/questions/2395715/resources-for-learning-monads-functors-monoids-arrows-etc, nhưng tôi đang tìm kiếm các cấu trúc ngoài funtors, functors applicative, monads, và mũi tên.

Chỉnh sửa: Tôi thừa nhận rằng functors ứng dụng nên được coi là "cấu trúc tính toán", nhưng tôi thực sự đang tìm kiếm thứ gì đó mà tôi chưa gặp. Điều này bao gồm functors ứng dụng, monads và mũi tên.

+0

Trang Monad vs. Arrows liên kết với một bài báo cũng so sánh các functors ứng dụng (còn gọi là thành ngữ). –

+0

Functors ứng dụng chắc chắn nhất * là * tốt ở tính toán composable! Trong thực tế, họ sáng tác tốt hơn so với monads (thành phần của hai functors ứng dụng là một functor ứng dụng, mà không giữ cho monads). – ehird

Trả lời

23

Arrows được tổng quát theo Danh mục và do đó, theo kiểu số Category.

class Category f where 
    (.) :: f a b -> f b c -> f a c 
    id :: f a a 

Arrow định nghĩa typeclass có Category làm lớp cha. Các thể loại (trong ý nghĩa haskell) tổng quát hóa các hàm (bạn có thể soạn chúng nhưng không áp dụng chúng) và do đó chắc chắn là một "mô hình tính toán". Arrow cung cấp Category với cấu trúc bổ sung để làm việc với bộ dữ liệu. Vì vậy, trong khi Category phản ánh điều gì đó về không gian chức năng của Haskell, Arrow mở rộng điều gì đó về loại sản phẩm.

Mỗi Monad phát sinh cái gì đó gọi là "Danh mục Kleisli" và cấu trúc này cung cấp cho bạn các phiên bản ArrowApply. Bạn có thể tạo một Monad trong số bất kỳ ArrowApply nào sao cho toàn bộ vòng tròn không thay đổi hành vi của bạn, do đó, theo một số ý nghĩa sâu, MonadArrowApply là giống nhau.

newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b } 

instance Monad m => Category (Kleisli m) where 
    id = Kleisli return 
    (Kleisli f) . (Kleisli g) = Kleisli (\b -> g b >>= f) 

instance Monad m => Arrow (Kleisli m) where 
    arr f = Kleisli (return . f) 
    first (Kleisli f) = Kleisli (\ ~(b,d) -> f b >>= \c -> return (c,d)) 
    second (Kleisli f) = Kleisli (\ ~(d,b) -> f b >>= \c -> return (d,c)) 

Trên thực tế mỗi Arrow làm phát sinh một Applicative (phổ định lượng để có được những loại phải) ngoài các Category lớp cha, và tôi tin rằng sự kết hợp của các hợp CategoryApplicative là đủ để tái tạo lại Arrow của bạn.

Vì vậy, các cấu trúc này được kết nối sâu sắc.

Cảnh báo: bình luận mơ hồ trước. Một sai số trung tâm giữa Functor/Applicative/Monad cách suy nghĩ và Category/Arrow cách suy nghĩ là trong khi Functor và ilk của nó là khái quát ở mức độ đối tượng (loại trong Haskell), Category/Arrow là generelazation của khái niệm về morphism (các chức năng trong Haskell). Niềm tin của tôi là suy nghĩ ở cấp độ tổng quát hình thái liên quan đến mức độ trừu tượng cao hơn so với suy nghĩ ở cấp độ tổng quát đối tượng. Đôi khi đó là một điều tốt, những lúc khác thì không. Mặt khác, mặc dù thực tế rằng Arrows có cơ sở phân loại và không có ai trong toán học nghĩ rằng Applicative thú vị, đó là sự hiểu biết của tôi rằng Applicative thường được hiểu rõ hơn Arrow.

Về cơ bản bạn có thể nghĩ đến "Thể loại < mũi tên < ArrowApply" và "functor < applicative < Monad" như vậy mà "Thể loại ~ functor", "mũi tên ~ applicative" và "ArrowApply ~ Monad".

More bê tông Dưới: Đối với các cấu trúc khác để mô hình tính toán: người ta thường có thể đảo ngược sự chỉ đạo của "mũi tên" (chỉ có nghĩa morphisms đây) trong công trình xây dựng phân loại để có được những "kép" hay "đồng xây dựng ". Vì vậy, nếu một đơn nguyên được định nghĩa là

class Functor m => Monad m where 
    return :: a -> m a 
    join :: m (m a) -> m a 

(okay, tôi biết rằng không phải là cách Haskell định nghĩa sự vật, nhưng ma >>= f = join $ fmap f majoin x = x >>= id nên nó chỉ cũng có thể là) thì comonad là

class Functor m => Comonad m where 
    extract :: m a -> a -- this is co-return 
    duplicate :: m a -> m (m a) -- this is co-join 

Điều này hóa ra lại khá phổ biến. Nó chỉ ra rằng Comonad là cấu trúc cơ bản cơ bản của automata di động. Đối với completness, tôi phải chỉ ra rằng Edward Kmett của Control.Comonad puts duplicate trong một lớp học giữa functor và Comonad cho "Nối Dài functors" bởi vì bạn cũng có thể định nghĩa

extend :: (m a -> b) -> m a -> m b -- Looks familiar? this is just the dual of >>= 
    extend f = fmap f . duplicate 
    --this is enough 
    duplicate = extend id 

Nó chỉ ra rằng tất cả Monad s cũng là "Nối Dài"

monadDuplicate :: Monad m => m a -> m (m a) 
    monadDuplicate = return 

trong khi tất cả Comonads là "Joinable"

comonadJoin :: Comonad m => m (m a) -> m a 
    comonadJoin = extract 

nên những cấu trúc này rất gần nhau.

+0

Tuyệt vời, loại và comonads là hai chủ đề tiếp theo của tôi. Cảm ơn. –

+0

Ah Tôi yêu bạn ví dụ về điện thoại di động. Edward Kmett có một bài viết rất thú vị về việc biến mọi comonad thành một đơn vị trong Haskell, nhưng không phải là một cách khác. [link] (http://comonad.com/reader/2011/monads-from-comonads/). Công cụ này rất cao, nhưng nếu bạn dành thời gian nó sẽ làm cho bạn hiểu được mối liên hệ giữa hai người. –

+1

@EdgarKlerks một trong những hậu quả của bài đăng mà tôi nghĩ là thú vị nhất là đồng đội 'Store' có thể khá cơ bản: vì Ống kính chỉ là" đồng đại số của cửa hàng comonad "(còn gọi là 'Lens ab = a -> Cửa hàng ba) 'và' Nhà nước' là những gì bạn nhận được bằng cách lấy kết thúc của comonad cửa hàng. Giữa ống kính và nhà nước bạn có một cái gì đó rất nhiều như lập trình bắt buộc. Tôi vẫn cảm thấy xa cách hiểu được ý nghĩa của điều này. –

8

Tất cả các Monads là Mũi tên (Monad is isorphorphic to ArrowApply). Theo một cách khác, tất cả Monads là trường hợp của Applicative, trong đó <*>Control.Monad.ap*>>>. Ứng dụng yếu hơn vì nó không đảm bảo hoạt động >>=. Do đó Applicative bắt các tính toán không kiểm tra các kết quả trước đó và phân nhánh trên các giá trị. Nhìn lại nhiều mã monadic thực sự là ứng dụng, và với một viết lại sạch sẽ điều này sẽ xảy ra.

Mở rộng đơn vị, với các loại Hạn chế gần đây trong GHC 7.4.1 giờ đây có thể có thiết kế đẹp hơn cho restricted monads. Và cũng có những người đang xem parameterized monads và dĩ nhiên tôi bao gồm liên kết tới một cái gì đó theo số Oleg.

+0

Có, monads là (gần) một chuyên môn của mũi tên và tổng quát của các ứng dụng. Có bất kỳ khái quát nào về các monads không phải là các mũi tên không? Có lẽ một cái gì đó mà generalizes mũi tên? –

4

Trong thư viện, các cấu trúc này làm phát sinh các loại tính toán khác nhau.

Ví dụ: Các ứng dụng có thể được sử dụng để thực hiện các hiệu ứng tĩnh. Với đó tôi có nghĩa là hiệu ứng, được định nghĩa ở phía trước. Ví dụ: khi triển khai máy trạng thái, từ chối hoặc chấp nhận trạng thái đầu vào. Họ không thể được sử dụng để thao tác cấu trúc nội bộ của họ về đầu vào của họ.

Kiểu nói lên tất cả:

<*> :: f (a -> b) -> f a -> f b 

Nó rất dễ dàng để lý trí, cấu trúc của f không thể phụ thuộc om đầu vào của a. Bởi vì không thể đạt đến f ở cấp độ loại.

Đơn vị quảng cáo có thể được sử dụng cho các hiệu ứng động. Điều này cũng có thể được lý luận từ chữ ký loại:

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

Làm thế nào bạn có thể thấy điều này? Bởi vì một là trên cùng một "cấp độ" như m. Về mặt toán học, nó là một quá trình hai giai đoạn. Ràng buộc là một thành phần của hai hàm: fmap và join. Trước tiên, chúng tôi sử dụng fmap cùng với hành động đơn thuần để tạo cấu trúc mới được nhúng trong cấu trúc cũ:

fmap :: (a -> b) -> m a -> m b 
f :: (a -> m b) 
m :: m a 
fmap f :: m a -> m (m b) 
fmap f m :: m (m b) 

Fmap có thể tạo cấu trúc mới, dựa trên giá trị đầu vào. Sau đó, chúng tôi sụp đổ cơ cấu với tham gia, do đó chúng tôi có thể để thao tác cấu trúc từ bên trong tính toán monadic trong một cách mà phụ thuộc vào đầu vào:

join :: m (m a) -> m a 
join (fmap f m) :: m b 

Nhiều monads được dễ dàng hơn để thực hiện với tham gia:

(>>=) = join . fmap 

này có thể với monads:

addCounter :: Int -> m Int() 

nhưng không phải với applicatives, nhưng applicatives (và bất kỳ đơn nguyên) có thể làm những việc như:

addOne :: m Int() 

Mũi tên cung cấp quyền kiểm soát nhiều hơn đối với đầu vào và loại đầu ra, nhưng đối với tôi, chúng thực sự cảm thấy tương tự như các ứng dụng. Có lẽ tôi sai về điều đó.

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