Подтвердить что ты не робот

Is 'data PoE a = Empty | Пара аа 'монады?

Этот вопрос исходит из этого ответа в примере функтора, который является аппликативным, но не монадой. Утверждается, что

data PoE a = Empty | Pair a a deriving (Functor,Eq)

не может иметь экземпляр монады, но я не вижу этого:

instance Applicative PoE where
    pure x = Pair x x
    Pair f g <*> Pair x y = Pair (f x) (g y)
    _        <*> _        = Empty
instance Monad PoE where
    Empty    >>= _ = Empty
    Pair x y >>= f = case (f x, f y) of 
                       (Pair x' _,Pair _ y') -> Pair x' y'
                       _ -> Empty

Фактическая причина, по которой я считаю, что это монада, состоит в том, что она изоморфна Maybe (Pair a) с Pair a = P aa. Они оба являются монадами, оба являются общими, поэтому их композиция также должна формировать монаду. О, я только узнал не всегда.

Какой контр-пример не соответствует закону монады? (и как найти это систематически?)


Я не ожидал такого интереса к этому вопросу. Теперь я должен решить, принимаю ли я лучший пример или лучший ответ на "систематическую" часть.

Между тем, я хочу визуализировать, как join работает для более простой Pair a = P aa:

                   P
          ________/ \________
         /                   \
        P                     P
       / \                   / \
      1   2                 3   4

он всегда принимает внешний путь, давая P 1 4, более широко известный как диагональ в матричном представлении. Для монадской ассоциации мне нужны три измерения, визуализация дерева работает лучше. Взятый из ответа на ци, это неудачный пример объединения, и как я могу его понять.

                  Pair
          _________/\_________
         /                    \
       Pair                   Pair
        /\                     /\
       /  \                   /  \
    Pair  Empty           Empty  Pair
     /\                           /\
    1  2                         3  4

Теперь вы join. fmap join join. fmap join, сначала сбрасывая нижние уровни, для join. join join. join коллапса из корня.

4b9b3361

Ответ 1

По-видимому, это не монада. Один из законов монада " join "

join . join = join . fmap join

Следовательно, согласно вышеприведенному закону, эти два выхода должны быть равны, но они не являются.

main :: IO ()
main = do
  let x = Pair (Pair (Pair 1 2) Empty) (Pair Empty (Pair 7 8))
  print (join . join $ x)
  -- output: Pair 1 8
  print (join . fmap join $ x)
  -- output: Empty

Проблема в том, что

join x      = Pair (Pair 1 2) (Pair 7 8)
fmap join x = Pair Empty Empty

Выполнение дополнительного join на них не делает их равными.

как найти это систематически?

join. join join. join имеет тип m (m (ma)) → m (ma), поэтому я начал с тройной Pair -of- Pair -of- Pair, используя номера 1..8. Это сработало хорошо. Затем я попытался вставить несколько Empty внутри и быстро нашел встречный пример выше.

Такой подход был возможен, так как m (m (m Int)) содержит только конечное количество целых чисел внутри, и у нас есть только конструкторы Pair и Empty чтобы попробовать.

Для этих проверок я считаю, что закон join легче тестировать, чем, скажем, ассоциативность >>=.

Ответ 2

QuickCheck немедленно находит контрпример для ассоциативности.

{-# LANGUAGE DeriveFunctor #-}

import Test.QuickCheck

data PoE a = Empty | Pair a a deriving (Functor,Eq, Show)

instance Applicative PoE where
    pure x = Pair x x
    Pair f g <*> Pair x y = Pair (f x) (g y)
    _        <*> _        = Empty
instance Monad PoE where
    Empty    >>= _ = Empty
    Pair x y >>= f = case (f x, f y) of 
                       (Pair x' _,Pair _ y') -> Pair x' y'
                       _ -> Empty

instance Arbitrary a => Arbitrary (PoE a) where
  arbitrary = oneof [pure Empty, Pair <$> arbitrary <*> arbitrary]

prop_assoc :: PoE Bool -> (Bool -> PoE Bool) -> (Bool -> PoE Bool) -> Property
prop_assoc m k h =
  ((m >>= k) >>= h) === (m >>= (\a -> k a >>= h))

main = do
  quickCheck $ \m (Fn k) (Fn h) -> prop_assoc m k h

Вывод:

*** Failed! Falsifiable (after 35 tests and 3 shrinks):    
Pair True False
{False->Pair False False, True->Pair False True, _->Empty}
{False->Pair False True, _->Empty}
Pair False True /= Empty

Ответ 3

Поскольку вы заинтересованы в том, как это сделать систематически, вот как я нашел контрпример с помощью quickcheck:

{-# LANGUAGE DeriveFunctor #-}

import Control.Monad ((>=>))
import Test.QuickCheck

-- <your code>

Определение произвольного экземпляра для генерации случайных PoE s.

instance (Arbitrary a) => Arbitrary (PoE a) where
    arbitrary = do
      emptyq <- arbitrary
      if emptyq
        then return Empty
        else Pair <$> arbitrary <*> arbitrary

И тесты для законов монады:

prop_right_id m = (m >>= return) == m
    where
    _types = (m :: PoE Int)

prop_left_id fun x = (return x >>= f) == f x
    where
    _types = fun :: Fun Int (PoE Int)
    f = applyFun fun

prop_assoc fun gun hun x = (f >=> (g >=> h)) x == ((f >=> g) >=> h) x
    where
    _types = (fun :: Fun Int (PoE Int),
              gun :: Fun Int (PoE Int),
              hun :: Fun Int (PoE Int),
              x :: Int)
    f = applyFun fun
    g = applyFun gun
    h = applyFun hun

Я не получаю никаких сбоев в отношении законов о личности, но prop_assoc действительно создает контрпример:

ghci> quickCheck prop_assoc
*** Failed! Falsifiable (after 7 tests and 36 shrinks):
{6->Pair 1 (-1), _->Empty}
{-1->Pair (-3) (-4), 1->Pair (-1) (-2), _->Empty}
{-3->Empty, _->Pair (-2) (-4)}
6

Не то чтобы это ужасно полезно для понимания того, почему происходит сбой, он дает вам место для начала. Если мы внимательно посмотрим, мы увидим, что мы передаем (-3) и (-2) третьей функции; (-3) отображает в Empty и (-2) отображает на Pair, поэтому мы не можем откладывать на законы любой из двух монад PoE.

Ответ 4

Этот тип потенциального экземпляра Monad можно кратко описать как "взятие диагонали". Легче понять, почему, если мы используем презентацию join. Здесь вы join к типу Pair вы упоминаете:

join (P (P a00 a11) (P a10 a11)) = P a00 a11

Однако брать диагональ гарантирует только законное join для фиксированных (или бесконечных) списков. Это из-за закона ассоциативности:

join . join = join . fmap join

Если n-й список в списке списков не имеет n-го элемента, это приведет к обрезке диагонали: она закончится до n-го элемента. join. join join. join сначала берет внешнюю диагональ (списка списков списков), а join. fmap join join. fmap join принимает внутренние диагонали. Возможно, что недостаточно длинный внутренний список, который не находится во внешней диагонали, чтобы обрезать join. fmap join join. fmap join, но это не может повлиять на join. join join. join. (Это было бы проще показать с изображением вместо слов.)

Ваш PoE является типом списка, который не имеет фиксированной длины (длина равна нулю или двум). Оказывается, что взятие его диагонали не дает нам монады, поскольку обсуждаемая выше потенциальная проблема действительно мешает (как показано в ответе ци).

Дополнительные замечания:

  • Именно по этой причине ZipList не является монадой: поведение zippy сводится к диагонали.

  • Бесконечные списки изоморфны функциям из натуральных, а списки фиксированной длины изоморфны функциям от натуральных до подходящего значения. Это означает, что вы можете получить экземпляр Monad для них из экземпляра для функций - и экземпляр, который вы получаете, опять-таки, сводится к диагонали.

  • Когда-то я смутился по поводу этой точной проблемы.

Ответ 5

(Проводя это как отдельный ответ, так как он немного перекрывается с моим другим).

Фактическая причина, по которой я считаю, что это монада, состоит в том, что она изоморфна Maybe (Pair a) с Pair a = P aa. Они оба являются монадами, оба являются общими, поэтому их композиция также должна формировать монаду. О, я только узнал не всегда.

Условия составления монад m -over- n с n пересекающимися:

-- Using TypeApplications notation to make the layers easier to track.
sequenceA @n @m . pure @n = fmap @m (pure @n)
sequenceA @n @m . fmap @n (join @m)
    = join @m . fmap @m (sequenceA @n @m) . sequenceA @n @m
sequenceA @n @m . join @n
    = fmap @m (join @n) . sequenceA @n @m . fmap @n (sequenceA @n @m)

(Существует также sequenceA @n @m. fmap @n (pure @m) = pure @m, но это всегда выполняется.)

В нашем случае мы имеем m ~ Maybe и n ~ Pair. Соответствующие определения методов для Pair будут:

fmap f (P x y) = P (f x) (f y)
pure x = P x x
P f g <*> P x y = P (f x) (g y)
join (P (P a00 a01) (P a10 a11)) = P a00 a11 -- Let pretend join is a method.
sequenceA (P x y) = P <$> x <*> y

Пусть проверяется третье свойство:

sequenceA @n @m . join @n
    = fmap @m (join @n) . sequenceA @n @m . fmap @n (sequenceA @n @m)

-- LHS
sequenceA . join $ P (P a00 a01) (P a10 a11)
sequenceA $ P a00 a11
P <$> a00 <*> a11 -- Maybe (Pair a)

-- RHS
fmap join . sequenceA . fmap sequenceA $ P (P a00 a01) (P a10 a11)
fmap join . sequenceA $ P (P <$> a00 <*> a01) (P <$> a10 <*> a11)
fmap join $ P <$> (P <$> a00 <*> a01) <*> (P <$> a10 <*> a11)
fmap join $ (\x y z w -> P (P x y) (P z w)) <$> a00 <*> a01 <*> a10 <*> a11
(\x _ _ w -> P x w) <$> a00 <*> a01 <*> a10 <*> a11 -- Maybe (Pair a)

Это явно не то же самое: в то время как любые a значения будут нарисованы исключительно из a00 и a11, эффектов a01 и a10, игнорируется в левой стороне, но не в правом (других словах, если a01 или a10 Nothing, RHS будет Nothing, но LHS не обязательно будет так). LHS точно соответствует исчезновению Empty в ответе chi, а RHS соответствует внутренней диагональной обрезке, описанной в моем другом ответе.


PS: Я забыл показать, что потенциальный пример, о котором мы говорим, является тем же самым, который обсуждается в вопросе:

join' ::  m (n (m (n a))) -> m (n a)
join' = fmap @m (join @n) . join @m . fmap @m (sequenceA @n @m)

С m ~ Maybe и n ~ Pair мы имеем:

join' :: Maybe (Pair (Maybe (Pair a))) -> Maybe (Pair a)
join' = fmap @Maybe (join @Pair) . join @Maybe . fmap @Maybe (sequenceA @Pair @Maybe)

join @Maybe. fmap @Maybe (sequenceA @Pair @Maybe) join @Maybe. fmap @Maybe (sequenceA @Pair @Maybe) означает, что join' приведет к Nothing если нет Nothing нигде:

join' = \case
    Just (P (Just (P a00 a01)) (Just (P a10 a11))) -> _
    _ -> Nothing

Разработка non- Nothing касается просто:

fmap join . join . fmap sequenceA $ Just (P (Just (P a00 a01)) (Just (P a10 a11)))
fmap join . join $ Just (Just (P (P a00 a01) (P a10 a11)))
fmap join $ Just (P (P a00 a01) (P a10 a11))
Just (P a00 a11)

Следовательно...

join' = \case
    Just (P (Just (P a00 _)) (Just (P _ a11))) -> Just (P a00 a11)
    _ -> Nothing

... что по существу то же самое, что:

join = \case
    Pair (Pair a00 _) (Pair _ a11) -> Pair (a00 a11)
    _ -> Empty