Đây là một lập luận ủng hộ rộng rãi cho ý tưởng đẹp của bạn.
Phần một: mapMaybe
Kế hoạch của tôi ở đây là khôi phục vấn đề về mặt mapMaybe, hy vọng rằng làm như vậy sẽ đưa chúng ta đến một nền tảng quen thuộc hơn. Để làm như vậy, tôi sẽ sử dụng một vài Eitherchức năng tiện ích:
maybeToRight :: a -> Maybe b -> Either a b
rightToMaybe :: Either a b -> Maybe b
leftToMaybe :: Either a b -> Maybe a
flipEither :: Either a b -> Either b a
(Tôi mất ba tên đầu tiên từ relude , và thứ tư từ lỗi . Bằng cách này, lỗi Mời maybeToRightvà rightToMaybenhư notevà hushtương ứng trong Control.Error.Util.)
Như bạn đã lưu ý, mapMaybecó thể được định nghĩa theo partition:
mapMaybe :: Filterable f => (a -> Maybe b) -> f a -> f b
mapMaybe f = snd . partition . fmap (maybeToRight () . f)
Điều quan trọng, chúng ta cũng có thể đi theo con đường khác:
partition :: Filterable f => f (Either a b) -> (f a, f b)
partition = mapMaybe leftToMaybe &&& mapMaybe rightToMaybe
Điều này cho thấy nó có ý nghĩa để làm lại luật của bạn về mặt mapMaybe. Với luật định danh, làm như vậy cho chúng ta một lý do tuyệt vời để quên hoàn toàn về trivial:
-- Left and right unit
mapMaybe rightToMaybe . fmap (bwd elunit) = id -- [I]
mapMaybe leftToMaybe . fmap (bwd erunit) = id -- [II]
Đối với tính kết hợp, chúng ta có thể sử dụng rightToMaybevà leftToMaybephân chia luật theo ba phương trình, một cho mỗi thành phần chúng ta nhận được từ các phân vùng kế tiếp nhau:
-- Associativity
mapMaybe rightToMaybe . fmap (bwd eassoc)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe -- [III]
mapMaybe rightToMaybe . mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe rightToMaybe -- [IV]
mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe leftToMaybe -- [V]
Phương tiện tham số mapMaybelà bất khả tri đối với các Eithergiá trị chúng ta đang giải quyết ở đây. Điều đó là như vậy, chúng ta có thể sử dụng kho vũ khí Eitherđồng phân nhỏ của mình để xáo trộn mọi thứ xung quanh và cho thấy [I] tương đương với [II] và [III] tương đương với [V]. Bây giờ chúng tôi xuống đến ba phương trình:
mapMaybe rightToMaybe . fmap (bwd elunit) = id -- [I]
mapMaybe rightToMaybe . fmap (bwd eassoc)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe -- [III]
mapMaybe rightToMaybe . mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe rightToMaybe -- [IV]
Tham số cho phép chúng ta nuốt fmapvào [I]:
mapMaybe (rightToMaybe . bwd elunit) = id
Tuy nhiên, điều đó chỉ đơn giản là ...
mapMaybe Just = id
... đó là tương đương với định luật bảo toàn / danh từ witherable 'sFilterable :
mapMaybe (Just . f) = fmap f
Điều đó Filterablecũng có luật thành phần:
-- The (<=<) is from the Maybe monad.
mapMaybe g . mapMaybe f = mapMaybe (g <=< f)
Chúng ta cũng có thể rút ra điều này từ luật của chúng ta? Hãy bắt đầu từ [III] và, một lần nữa, có tham số thực hiện công việc của nó. Cái này khó hơn, vì vậy tôi sẽ viết nó đầy đủ:
mapMaybe rightToMaybe . fmap (bwd eassoc)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe -- [III]
-- f :: a -> Maybe b; g :: b -> Maybe c
-- Precomposing fmap (right (maybeToRight () . g) . maybeToRight () . f)
-- on both sides:
mapMaybe rightToMaybe . fmap (bwd eassoc)
. fmap (right (maybeToRight () . g) . maybeToRight () . f)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe
. fmap (right (maybeToRight () . g) . maybeToRight () . f)
mapMaybe rightToMaybe . mapMaybe rightToMaybe
. fmap (right (maybeToRight () . g) . maybeToRight () . f) -- RHS
mapMaybe rightToMaybe . fmap (maybeToRight () . g)
. mapMaybe rightToMaybe . fmap (maybeToRight () . f)
mapMaybe (rightToMaybe . maybeToRight () . g)
. mapMaybe (rightToMaybe . maybeToRight () . f)
mapMaybe g . mapMaybe f
mapMaybe rightToMaybe . fmap (bwd eassoc)
. fmap (right (maybeToRight () . g) . maybeToRight () . f) -- LHS
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight () . g) . maybeToRight () . f)
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight ()) . maybeToRight () . fmap @Maybe g . f)
-- join @Maybe
-- = rightToMaybe . bwd eassoc . right (maybeToRight ()) . maybeToRight ()
mapMaybe (join @Maybe . fmap @Maybe g . f)
mapMaybe (g <=< f) -- mapMaybe (g <=< f) = mapMaybe g . mapMaybe f
Theo hướng khác:
mapMaybe (g <=< f) = mapMaybe g . mapMaybe f
-- f = rightToMaybe; g = rightToMaybe
mapMaybe (rightToMaybe <=< rightToMaybe)
= mapMaybe rightToMaybe . mapMaybe rightToMaybe
mapMaybe (rightToMaybe <=< rightToMaybe) -- LHS
mapMaybe (join @Maybe . fmap @Maybe rightToMaybe . rightToMaybe)
-- join @Maybe
-- = rightToMaybe . bwd eassoc . right (maybeToRight ()) . maybeToRight ()
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight ()) . maybeToRight ()
. fmap @Maybe rightToMaybe . rightToMaybe)
mapMaybe (rightToMaybe . bwd eassoc
. right (maybeToRight () . rightToMaybe)
. maybeToRight () . rightToMaybe)
mapMaybe (rightToMaybe . bwd eassoc) -- See note below.
mapMaybe rightToMaybe . fmap (bwd eassoc)
-- mapMaybe rightToMaybe . fmap (bwd eassoc)
-- = mapMaybe rightToMaybe . mapMaybe rightToMaybe
(Lưu ý: Mặc dù maybeToRight () . rightToMaybe :: Either a b -> Either () bkhông id, trong đạo hàm trên, các giá trị bên trái sẽ bị loại bỏ, vì vậy thật công bằng khi loại bỏ nó như thể nó là id.)
Vì vậy [III] tương đương với luật thành phần của witherable 's Filterable.
Tại thời điểm này, chúng ta có thể sử dụng luật thành phần để đối phó với [IV]:
mapMaybe rightToMaybe . mapMaybe leftToMaybe . fmap (bwd eassoc)
= mapMaybe leftToMaybe . mapMaybe rightToMaybe -- [IV]
mapMaybe (rightToMaybe <=< leftToMaybe) . fmap (bwd eassoc)
= mapMaybe (letfToMaybe <=< rightToMaybe)
mapMaybe (rightToMaybe <=< leftToMaybe . bwd eassoc)
= mapMaybe (letfToMaybe <=< rightToMaybe)
-- Sufficient condition:
rightToMaybe <=< leftToMaybe . bwd eassoc = letfToMaybe <=< rightToMaybe
-- The condition holds, as can be directly verified by substiuting the definitions.
Điều này đủ cho thấy số lượng lớp học của bạn với một công thức được thiết lập tốt Filterable, đó là một kết quả rất tốt đẹp. Đây là một bản tóm tắt của các luật:
mapMaybe Just = id -- Identity
mapMaybe g . mapMaybe f = mapMaybe (g <=< f) -- Composition
Như các tài liệu có thể khắc phục được , đây là các luật functor cho một functor từ Kleisli Có thể đến Hask .
Phần hai: Thay thế và Monad
Bây giờ chúng tôi có thể giải quyết câu hỏi thực tế của bạn, đó là về các đơn nguyên thay thế. Đề xuất của bạn partitionlà:
partitionAM :: (Alternative f, Monad f) => f (Either a b) -> (f a, f b)
partitionAM
= (either return (const empty) =<<) &&& (either (const empty) return =<<)
Theo kế hoạch rộng hơn của tôi, tôi sẽ chuyển sang mapMaybethuyết trình:
mapMaybe f
snd . partition . fmap (maybeToRight () . f)
snd . (either return (const empty) =<<) &&& (either (const empty) return =<<)
. fmap (maybeToRight () . f)
(either (const empty) return =<<) . fmap (maybeToRight () . f)
(either (const empty) return . maybeToRight . f =<<)
(maybe empty return . f =<<)
Và vì vậy chúng ta có thể định nghĩa:
mapMaybeAM :: (Alternative f, Monad f) => (a -> Maybe b) -> f a -> f b
mapMaybeAM f u = maybe empty return . f =<< u
Hoặc, trong một lỗi chính tả:
mapMaybeAM = (=<<) . (maybe empty return .)
Một vài đoạn trên, tôi lưu ý rằng các Filterableluật nói rằng đó mapMaybelà ánh xạ hình thái của một functor từ Kleisli Có thể đến Hask . Kể từ khi thành phần của functors là một functor, và (=<<)là ánh xạ cấu xạ của một functor từ Kleisli f để Hask , (maybe empty return .)là lập bản đồ cấu xạ của một functor từ Kleisli lẽ để Kleisli f đủ cho mapMaybeAMlà hợp pháp. Các luật functor có liên quan là:
maybe empty return . Just = return -- Identity
maybe empty return . g <=< maybe empty return . f
= maybe empty return . (g <=< f) -- Composition
Luật định danh này có hiệu lực, vì vậy hãy tập trung vào thành phần một:
maybe empty return . g <=< maybe empty return . f
= maybe empty return . (g <=< f)
maybe empty return . g =<< maybe empty return (f a)
= maybe empty return (g =<< f a)
-- Case 1: f a = Nothing
maybe empty return . g =<< maybe empty return Nothing
= maybe empty return (g =<< Nothing)
maybe empty return . g =<< empty = maybe empty return Nothing
maybe empty return . g =<< empty = empty -- To be continued.
-- Case 2: f a = Just b
maybe empty return . g =<< maybe empty return (Just b)
= maybe empty return (g =<< Just b)
maybe empty return . g =<< return b = maybe empty return (g b)
maybe empty return (g b) = maybe empty return (g b) -- OK.
Vì vậy, mapMaybeAMlà iff hợp pháp maybe empty return . g =<< empty = emptycho bất kỳ g. Bây giờ, nếu emptyđược định nghĩa là absurd <$> nil (), như bạn đã làm ở đây, chúng tôi có thể chứng minh điều đó f =<< empty = emptycho bất kỳ f:
f =<< empty = empty
f =<< empty -- LHS
f =<< absurd <$> nil ()
f . absurd =<< nil ()
-- By parametricity, f . absurd = absurd, for any f.
absurd =<< nil ()
return . absurd =<< nil ()
absurd <$> nil ()
empty -- LHS = RHS
Theo trực giác, nếu emptythực sự trống rỗng (vì nó phải như vậy, theo định nghĩa chúng ta đang sử dụng ở đây), sẽ không có giá trị nào fđược áp dụng, và do đó f =<< emptykhông thể dẫn đến bất cứ điều gì ngoài empty.
Một cách tiếp cận khác nhau ở đây sẽ là xem xét sự tương tác của các lớp Alternativevà Monad. Khi nó xảy ra, có một lớp cho các đơn nguyên thay thế : MonadPlus. Theo đó, một restyled mapMaybecó thể trông như thế này:
-- Lawful iff, for any f, mzero >>= maybe empty mzero . f = mzero
mmapMaybe :: MonadPlus m => (a -> Maybe b) -> m a -> m b
mmapMaybe f m = m >>= maybe mzero return . f
Mặc dù có nhiều ý kiến khác nhau về bộ luật nào là phù hợp nhất MonadPlus, một trong những luật dường như không ai phản đối là ...
mzero >>= f = mzero -- Left zero
... đó chính xác là tài sản của emptychúng tôi đã thảo luận về một vài đoạn trên. Tính hợp pháp của việc mmapMaybetuân theo ngay lập tức từ luật không bên trái.
(Ngẫu nhiên, Control.Monadcung cấpmfilter :: MonadPlus m => (a -> Bool) -> m a -> m a , phù hợp với những gì filterchúng ta có thể xác định bằng cách sử dụng mmapMaybe.)
Tóm tắt:
Nhưng việc thực hiện này luôn luôn hợp pháp? Có đôi khi hợp pháp (đối với một số định nghĩa chính thức của "đôi khi")?
Có, việc thực hiện là hợp pháp. Kết luận này xoay quanh việc emptythực sự trống rỗng, vì nó nên, hoặc trên đơn nguyên thay thế có liên quan theo MonadPlusluật không bên trái , điều này làm cho mọi thứ trở nên tương tự.
Điều đáng nhấn mạnh Filterablelà không bị thu hẹp bởi MonadPlus, như chúng ta có thể minh họa bằng các ví dụ sau:
ZipList: có thể lọc, nhưng không phải là một đơn nguyên. Các Filterableví dụ là giống như một cho danh sách, mặc dù Alternativemột là khác nhau.
Map: có thể lọc, nhưng không phải là đơn nguyên hay ứng dụng. Trong thực tế, Mapthậm chí không thể được áp dụng vì không có triển khai hợp lý pure. Nó, tuy nhiên, có riêng của nó empty.
MaybeT f: trong khi nó Monadvà các Alternativethể hiện yêu cầu fphải là một đơn nguyên, và một emptyđịnh nghĩa riêng biệt sẽ cần ít nhất Applicative, Filterablecá thể chỉ yêu cầu Functor f(mọi thứ đều có thể lọc được nếu bạn trượt một Maybelớp vào nó).
Phần ba: trống
Tại thời điểm này, người ta vẫn có thể tự hỏi vai trò của nó lớn như thế nào empty, hoặc nil, thực sự đóng vai trò gì Filterable. Nó không phải là một phương thức lớp, và hầu hết các trường hợp dường như có một phiên bản hợp lý của nó nằm xung quanh.
Một điều chúng ta có thể chắc chắn là, nếu loại có thể lọc có bất kỳ cư dân nào, thì ít nhất một trong số chúng sẽ là một cấu trúc trống, bởi vì chúng ta luôn có thể lấy bất kỳ cư dân nào và lọc mọi thứ ra:
chop :: Filterable f => f a -> f Void
chop = mapMaybe (const Nothing)
Sự tồn tại của chop, mặc dù không có nghĩa là sẽ có một giá trị trống duy nhất nil hoặc chopsẽ luôn đưa ra kết quả tương tự. Xem xét, ví dụ, MaybeT IO, mà Filterableví dụ có thể được dùng như một cách để kiểm duyệt kết quả IOtính toán. Trường hợp này là hoàn toàn hợp pháp, mặc dù chopcó thể tạo ra MaybeT IO Voidcác giá trị riêng biệt mang IOhiệu ứng tùy ý .
Ở một lưu ý cuối cùng, bạn đã ám chỉ đến khả năng làm việc với các functor đơn hình mạnh, do đó Alternativevà Filterableđược liên kết bằng cách tạo union/ partitionvà nil/ trivialđẳng cấu. Có unionvà partitionnhư các nghịch đảo lẫn nhau là có thể hiểu được nhưng khá hạn chế, cho rằng union . partitionloại bỏ một số thông tin về sự sắp xếp của các yếu tố cho một phần lớn các trường hợp. Đối với sự đồng hình khác, trivial . nillà tầm thường, nhưng nil . trivialthú vị ở chỗ nó ngụ ý chỉ có một f Voidgiá trị duy nhất , một cái gì đó giữ cho một phần lớn các Filterabletrường hợp. Nó xảy ra rằng có một MonadPlusphiên bản của điều kiện này. Nếu chúng ta yêu cầu điều đó, cho bất kỳ u...
absurd <$> chop u = mzero
... và sau đó thay thế mmapMaybetừ phần hai, chúng tôi nhận được:
absurd <$> chop u = mzero
absurd <$> mmapMaybe (const Nothing) u = mzero
mmapMaybe (fmap absurd . const Nothing) u = mzero
mmapMaybe (const Nothing) u = mzero
u >>= maybe mzero return . const Nothing = mzero
u >>= const mzero = mzero
u >> mzero = mzero
Khách sạn này được gọi là luật không quyền của MonadPlus, mặc dù có những lý do chính đáng để tranh chấp vị thế của nó như là một luật của hạng cụ thể đó.