Вот аргумент, который в целом поддерживает вашу прекрасную идею.
Часть первая: карта
Мой план здесь состоит в том, чтобы переформулировать проблему в терминах mapMaybe
, надеясь, что это приведет нас к более знакомой теме. Для этого я воспользуюсь несколькими служебными функциями Either
-жонглирования:
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
(Первые три имени я взял из повторно установить, а четвертый - от errors. Кстати, errors предлагает maybeToRight
и rightToMaybe
как note
и hush
соответственно в _ 8_.)
Как вы отметили, mapMaybe
можно определить в терминах partition
:
mapMaybe :: Filterable f => (a -> Maybe b) -> f a -> f b
mapMaybe f = snd . partition . fmap (maybeToRight () . f)
Важно отметить, что мы также можем пойти другим путем:
partition :: Filterable f => f (Either a b) -> (f a, f b)
partition = mapMaybe leftToMaybe &&& mapMaybe rightToMaybe
Это говорит о том, что имеет смысл изменить ваши законы с точки зрения mapMaybe
. Благодаря законам об идентичности это дает нам отличный повод полностью забыть о trivial
:
-- Left and right unit
mapMaybe rightToMaybe . fmap (bwd elunit) = id -- [I]
mapMaybe leftToMaybe . fmap (bwd erunit) = id -- [II]
Что касается ассоциативности, мы можем использовать rightToMaybe
и leftToMaybe
, чтобы разделить закон на три уравнения, по одному для каждого компонента, который мы получаем из последовательных разделов:
-- 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]
Параметричность означает, что mapMaybe
не зависит от Either
значений, с которыми мы здесь имеем дело. Если это так, мы можем использовать наш небольшой арсенал Either
изоморфизмов, чтобы перемешать вещи и показать, что [I] эквивалентно [II], а [III] эквивалентно [V]. Теперь мы свелись к трем уравнениям:
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]
Параметричность позволяет нам проглотить fmap
в [I]:
mapMaybe (rightToMaybe . bwd elunit) = id
Однако это просто ...
mapMaybe Just = id
... что эквивалентно закону сохранения / идентичности из Filterable
пользователя witherable:
mapMaybe (Just . f) = fmap f
У этого Filterable
также есть закон композиции:
-- The (<=<) is from the Maybe monad.
mapMaybe g . mapMaybe f = mapMaybe (g <=< f)
Можем ли мы также вывести это из наших законов? Начнем с [III] и еще раз заставим параметричность делать свое дело. Этот посложнее, поэтому я запишу его полностью:
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
В обратном направлении:
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
(Примечание: хотя maybeToRight () . rightToMaybe :: Either a b -> Either () b
не id
, в выводе выше левые значения в любом случае будут отброшены, поэтому будет справедливо вычеркнуть его, как если бы это было id
.)
Таким образом, [III] эквивалентен закону композиции Filterable
объекта witherable.
На этом этапе мы можем использовать закон композиции для работы с [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.
Этого достаточно, чтобы показать, что ваш класс соответствует устоявшейся формулировке Filterable
, что является очень хорошим результатом. Вот краткое изложение законов:
mapMaybe Just = id -- Identity
mapMaybe g . mapMaybe f = mapMaybe (g <=< f) -- Composition
Как отмечается в witherable документации, это законы функторов для функтора от Kleisli Maybe до Hask.
Часть вторая: альтернатива и монада
Теперь мы можем ответить на ваш вопрос об альтернативных монадах. Предложенная вами реализация partition
была:
partitionAM :: (Alternative f, Monad f) => f (Either a b) -> (f a, f b)
partitionAM
= (either return (const empty) =<<) &&& (either (const empty) return =<<)
Следуя своему более широкому плану, я перейду к mapMaybe
презентации:
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 =<<)
Итак, мы можем определить:
mapMaybeAM :: (Alternative f, Monad f) => (a -> Maybe b) -> f a -> f b
mapMaybeAM f u = maybe empty return . f =<< u
Или, без точки написания:
mapMaybeAM = (=<<) . (maybe empty return .)
Несколькими абзацами выше я заметил, что Filterable
законы гласят, что mapMaybe
является отображением морфизма функтора из Kleisli Maybe в Hask. Поскольку композиция функторов является функтором, а (=<<)
является отображением морфизма функтора из Клейсли f в Hask, (maybe empty return .)
является отображением морфизма функтора из Kleisli Maybe до Kleisli f достаточно, чтобы mapMaybeAM
было законным. Соответствующие законы функторов:
maybe empty return . Just = return -- Identity
maybe empty return . g <=< maybe empty return . f
= maybe empty return . (g <=< f) -- Composition
Этот закон идентичности выполняется, поэтому давайте сосредоточимся на композиционном:
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.
Следовательно, mapMaybeAM
законно тогда и только тогда, когда maybe empty return . g =<< empty = empty
для любого g
. Теперь, если empty
определен как absurd <$> nil ()
, как вы сделали здесь, мы можем доказать, что f =<< empty = empty
для любого 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
Интуитивно понятно, что если empty
действительно пусто (как и должно быть, учитывая определение, которое мы здесь используем), не будет значений для f
, к которым можно применить, и поэтому f =<< empty
не может привести ни к чему, кроме empty
.
Другой подход здесь был бы во взаимодействии классов Alternative
и Monad
. Как оказалось, существует класс для альтернативных монад: _ 66_. Соответственно, рестайлинговый mapMaybe
может выглядеть так:
-- 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
Хотя существуют разные мнения о том, какой свод законов наиболее подходит для MonadPlus
, один из законов, против которого, кажется, никто не возражает, это ...
mzero >>= f = mzero -- Left zero
... что в точности является свойством empty
, которое мы обсуждали несколькими абзацами выше. Законность mmapMaybe
следует непосредственно из закона левого нуля.
(Между прочим, _ 73_ предоставляет mfilter :: MonadPlus m => (a -> Bool) -> m a -> m a
, который соответствует filter
, который мы можем определить с помощью mmapMaybe
.)
В итоге:
Но всегда ли такая реализация законна? Законно ли это иногда (для некоторого формального определения «иногда»)?
Да, реализация законна. Этот вывод зависит от того, что empty
действительно пусто, как и должно быть, или от соответствующей альтернативной монады, соответствующей закону левого нуля MonadPlus
, который сводится примерно к одному и тому же.
Стоит подчеркнуть, что Filterable
не входит в MonadPlus
, что мы можем проиллюстрировать следующими контрпримерами:
ZipList
: фильтруемый, но не монада. Экземпляр Filterable
такой же, как и экземпляр для списков, хотя экземпляр Alternative
отличается.
Map
: фильтруемый, но ни монада, ни аппликатив. Фактически, Map
не может быть даже аппликативным, потому что не существует разумной реализации pure
. Однако у него есть свой empty
.
MaybeT f
: в то время как его экземпляры Monad
и Alternative
требуют, чтобы f
был монадой, а для изолированного empty
определения потребуется как минимум Applicative
, для экземпляра Filterable
требуется только Functor f
(все становится фильтруемым, если вы вставите в него слой Maybe
).
Часть третья: пусто
На этом этапе можно все еще задаться вопросом, насколько большую роль empty
или nil
действительно играет в Filterable
. Это не метод класса, и все же в большинстве случаев, похоже, есть разумная его версия.
Единственное, в чем мы можем быть уверены, это то, что если фильтруемый тип вообще имеет жителей, по крайней мере одно из них будет пустой структурой, потому что мы всегда можем взять любого жителя и отфильтровать все:
chop :: Filterable f => f a -> f Void
chop = mapMaybe (const Nothing)
Однако наличие chop
не означает, что будет одиночное nil
пустое значение или что chop
всегда будет выдавать один и тот же результат. Рассмотрим, например, MaybeT IO
, чей Filterable
экземпляр можно рассматривать как способ цензуры результатов IO
вычислений. Экземпляр является совершенно законным, хотя chop
может давать различные MaybeT IO Void
значения, которые несут произвольные IO
эффекты.
В заключение, вы упомянули возможность работы с сильные моноидальные функторы, так что Alternative
и Filterable
связаны путем создания изоморфизмов _112 _ / _ 113_ и _114 _ / _ 115_. Использование union
и partition
в качестве взаимных инверсий возможно, но довольно ограничивает, учитывая, что union . partition
отбрасывает некоторую информацию о расположении элементов для большой доли экземпляров. Что касается другого изоморфизма, trivial . nil
тривиален, но nil . trivial
интересен тем, что подразумевает, что существует только одно значение f Void
, что справедливо для значительной части Filterable
экземпляров. Бывает, что есть MonadPlus
версия этого условия. Если мы того потребуем, для любого _124 _...
absurd <$> chop u = mzero
... а затем подставляем mmapMaybe
из второй части, получаем:
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
Это свойство известно как закон нулевого права MonadPlus
, хотя есть веские причины оспорить его статус закона. этого конкретного класса.
person
duplode
schedule
22.03.2020
Filterable
довольно слабы). @AsadSaeeduddin Подумайте о том, чтобы научиться некоторым интерактивным навыкам доказательства теорем, чтобы вы могли расширить возможности использования, а не свой умственный склад ума, на доказательства! - person Li-yao Xia   schedule 18.03.2020