Đâ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 Either
chứ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 maybeToRight
và rightToMaybe
như note
và hush
tương ứng trong Control.Error.Util
.)
Như bạn đã lưu ý, mapMaybe
có 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 rightToMaybe
và leftToMaybe
phâ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ố mapMaybe
là bất khả tri đối với các Either
giá 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 fmap
và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 đó Filterable
cũ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 () b
khô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 partition
là:
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 mapMaybe
thuyế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 Filterable
luật nói rằng đó mapMaybe
là á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 mapMaybeAM
là 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, mapMaybeAM
là iff hợp pháp maybe empty return . g =<< empty = empty
cho 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 = empty
cho 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 empty
thự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 =<< empty
khô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 Alternative
và Monad
. Khi nó xảy ra, có một lớp cho các đơn nguyên thay thế : MonadPlus
. Theo đó, một restyled mapMaybe
có 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 empty
chú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 mmapMaybe
tuân theo ngay lập tức từ luật không bên trái.
(Ngẫu nhiên, Control.Monad
cung cấpmfilter :: MonadPlus m => (a -> Bool) -> m a -> m a
, phù hợp với những gì filter
chú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 empty
thực sự trống rỗng, vì nó nên, hoặc trên đơn nguyên thay thế có liên quan theo MonadPlus
luậ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 Filterable
là 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 Filterable
ví dụ là giống như một cho danh sách, mặc dù Alternative
mộ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ế, Map
thậ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ó Monad
và các Alternative
thể hiện yêu cầu f
phả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
, Filterable
cá 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 Maybe
lớ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 chop
sẽ luôn đưa ra kết quả tương tự. Xem xét, ví dụ, MaybeT IO
, mà Filterable
ví dụ có thể được dùng như một cách để kiểm duyệt kết quả IO
tính toán. Trường hợp này là hoàn toàn hợp pháp, mặc dù chop
có thể tạo ra MaybeT IO Void
các giá trị riêng biệt mang IO
hiệ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 đó Alternative
và Filterable
được liên kết bằng cách tạo union
/ partition
và nil
/ trivial
đẳng cấu. Có union
và partition
như các nghịch đảo lẫn nhau là có thể hiểu được nhưng khá hạn chế, cho rằng union . partition
loạ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 . nil
là tầm thường, nhưng nil . trivial
thú vị ở chỗ nó ngụ ý chỉ có một f Void
giá trị duy nhất , một cái gì đó giữ cho một phần lớn các Filterable
trường hợp. Nó xảy ra rằng có một MonadPlus
phiê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ế mmapMaybe
từ 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ể đó.