2011-09-10 24 views
6

Tôi đã cố gắng "tìm hiểu tôi một Haskell" thông qua cuốn sách trực tuyến LYAH.một sự tổng quát đơn giản của lớp loại ứng dụng (Functor); phù hợp với mô hình trên các nhà xây dựng

Tác giả mô tả hành vi của Functors of the Applicative type là loại có khả năng trích xuất một hàm từ một functor và ánh xạ nó trên một hàm thứ hai; này là thông qua việc < *> chức năng kê khai kiểu lớp applicative:

class (Functor f) => Applicative f where 
    pure :: a -> f a 
    (<*>) :: f (a -> b) -> f a -> f b 

Là một ví dụ đơn giản, kiểu lẽ ​​là một thể hiện của applicative dưới thực hiện như sau:

instance Applicative Maybe where 
    pure = Just 
    Nothing <*> _ = Nothing 
    (Just f) <*> something = fmap f something 

Một ví dụ về hành vi đã đề cập trước:

ghci> Just (*2) <*> Just 10   -- evaluates to Just 20 

nên < *> điều hành "chiết xuất" các (* 2) chức năng từ các toán hạng đầu tiên và bản đồ nó trên toán hạng thứ hai.

Bây giờ trong các loại ứng dụng, cả hai toán hạng < *> đều có cùng loại, vì vậy tôi nghĩ là tập thể dục tại sao không thử thực hiện khái quát hóa hành vi này, trong đó hai toán hạng là Functors của các loại khác nhau đánh giá một cái gì đó như thế này:

Just (2*) <*:*> [1,2,3,4] -- should evaluate to [2,4,6,8] 

Vì vậy, đây là những gì tôi đã đưa ra:

import Control.Applicative 

class (Applicative f, Functor g) => DApplicative f g where 
    pure1 :: a -> f a 
    pure1 = pure 
    (<*:*>) :: f (a -> b) -> g a -> g b  -- referred below as (1) 

instance DApplicative Maybe [] where -- an "instance pair" of this class 
    (Just func) <*:*> g = fmap func g 

main = do putStrLn(show x) 
    where x = Just (2*) <*:*> [1,2,3,4] -- it works, x equals [2,4,6,8] 

Bây giờ, mặc dù các công trình trên, tôi đang tự hỏi nếu chúng ta có thể làm tốt hơn; là nó có thể cung cấp cho một thực hiện mặc định cho < *: *> có thể được áp dụng cho một loạt các f & g cặp, trong tuyên bố cho DApplicative f g chính nó? Và điều này dẫn tôi đến câu hỏi sau: Có cách nào để khớp mẫu trên các nhà xây dựng trên các loại dữ liệu khác nhau không?

Tôi hy vọng câu hỏi của mình có ý nghĩa và tôi không chỉ phun vô nghĩa (nếu tôi, xin đừng quá khắc nghiệt; Tôi chỉ là người mới bắt đầu FP lên đường trước giờ đi ngủ của mình ...)

Trả lời

5

Điều này có ý nghĩa, nhưng nó kết thúc không đặc biệt hữu ích trong biểu mẫu hiện tại của nó. Vấn đề là chính xác những gì bạn đã nhận thấy: không có cách nào để cung cấp một mặc định mà những điều hợp lý với các loại khác nhau, hoặc thường chuyển đổi từ f đến g. Vì vậy, bạn sẽ có một vụ nổ bậc hai trong số trường hợp bạn cần phải viết.

Bạn chưa hoàn thành phiên bản DApplicative. Dưới đây là một việc thực hiện đầy đủ cho Maybe[]:

instance DApplicative Maybe [] where -- an "instance pair" of this class 
    (Just func) <*:*> g = fmap func g 
    Nothing  <*:*> g = [] 

này kết hợp hành vi của cả hai Maybe[], bởi vì với Just nó làm những gì bạn mong đợi, nhưng với Nothing nó sẽ trả về không có gì, một danh sách rỗng.

Vì vậy, thay vì viết DApplicative có hai loại khác nhau, điều gì sẽ xảy ra nếu bạn có cách kết hợp hai ứng dụng fg thành một loại duy nhất? Nếu bạn khái quát hành động này, bạn có thể sử dụng tiêu chuẩn Applicative với loại mới.

Điều này có thể được thực hiện với việc xây dựng tiêu chuẩn của Applicatives như

liftAp :: f (g (a -> b)) -> f (g a) -> f (g b) 
liftAp l r = (<*>) <$> l <*> r 

nhưng thay vào đó chúng ta hãy thay đổi Maybe:

import Control.Applicative 

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

instance (Functor f) => Functor (MaybeT f) where 
    fmap f (MaybeT m) = MaybeT ((fmap . fmap) f m) 

instance (Applicative f) => Applicative (MaybeT f) where 
    pure a = MaybeT (pure (pure a)) 
    (MaybeT f) <*> (MaybeT m) = MaybeT ((<*>) <$> f <*> m) 

Bây giờ bạn chỉ cần một cách để chuyển đổi điều gì đó trong applicative bên trong, f , vào đơn đăng ký kết hợp MaybeT f:

lift :: (Functor f) => f a -> MaybeT f a 
lift = MaybeT . fmap Just 

Điều này trông giống như rất nhiều boilerplate, nhưng ghc có thể tự động lấy được gần như tất cả của nó.

Bây giờ bạn có thể dễ dàng sử dụng các chức năng kết hợp:

*Main Control.Applicative> runMaybeT $ pure (*2) <*> lift [1,2,3,4] 
[Just 2,Just 4,Just 6,Just 8] 

*Main Control.Applicative> runMaybeT $ MaybeT (pure Nothing) <*> lift [1,2,3,4] 
[Nothing,Nothing,Nothing,Nothing] 

Hành vi này tại Không có gì có thể ngạc nhiên, nhưng nếu bạn nghĩ về một danh sách như đại diện indeterminism có lẽ bạn có thể thấy nó như thế nào có thể hữu ích. Nếu bạn muốn hành vi kép trả về Just [a] hoặc Nothing, bạn chỉ cần Danh sách được chuyển đổi ListT Maybe a.

Điều này không hoàn toàn giống với trường hợp tôi đã viết cho DApplicative. Lý do là vì các loại. DApplicative chuyển đổi số f thành số g. Điều đó chỉ có thể xảy ra khi bạn biết cụ thể fg. Để khái quát hóa kết quả, kết quả cần phải kết hợp các hành vi của cả hai số fg khi triển khai này.

Tất cả điều này cũng hoạt động với Monads. Các loại được chuyển đổi như MaybeT được cung cấp bởi các thư viện biến áp đơn lẻ như mtl.

+0

cảm ơn câu trả lời toàn diện ... Tôi chưa đọc kỹ mã của bạn, nhưng tôi có ý tưởng chung. – Aky

1

Ví dụ DApplicative của bạn cho Maybe chưa hoàn thành: Điều gì sẽ xảy ra nếu đối số đầu tiên của <*:*>Nothing?

Lựa chọn phải làm cho mọi kết hợp không rõ ràng. Đối với hai danh sách, <*> sẽ tạo tất cả các kết hợp: [(+2),(+10)] <*> [3,4] cho số [5,6,13,14]. Đối với hai số ZipList, bạn có hành vi giống như mã zip: (ZipList [(+2),(+10)]) <*> (ZipList [3,4]) cho số [5,14]. Vì vậy, bạn phải chọn một trong hai hành vi có thể có cho một DApplicative của danh sách và ZipList, không có phiên bản "chính xác".

+0

@Daniel Wagner: Cảm ơn! Đã sửa. – Landei

+0

đó là một quan sát tốt đẹp – Aky

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