[haskell] 모나드가 컴포지션에서 닫히지 않음을 보여주는 구체적인 예 (증거 포함)?

응용 펑 터는 컴포지션에서 닫히지 만 모나드는 그렇지 않다는 것은 잘 알려져 있습니다. 그러나 모나드가 항상 구성하는 것은 아니라는 구체적인 반례를 찾는 데 어려움을 겪었습니다.

이 대답[String -> a]비 모나드의 예입니다. 잠시 놀아 본 후 직관적으로 믿었지만 그 대답은 실제로 정당화하지 않고 “조인을 구현할 수 없습니다”라고 말합니다. 좀 더 형식적인 것을 원합니다. 물론 type과 함께 많은 함수가 있습니다 [String -> [String -> a]] -> [String -> a]. 그러한 함수가 반드시 모나드 법칙을 만족하지 않는다는 것을 보여 주어야합니다.

모든 예 (증거 포함)가 가능합니다. 특히 위의 예에 대한 증거를 찾는 것은 아닙니다.



답변

모나드와 동형 인 다음 모나드를 고려하십시오 (Bool ->).

data Pair a = P a a

instance Functor Pair where
  fmap f (P x y) = P (f x) (f y)

instance Monad Pair where
  return x = P x x
  P a b >>= f = P x y
    where P x _ = f a
          P _ y = f b

Maybe모나드로 구성합니다 .

newtype Bad a = B (Maybe (Pair a))

나는 그것이 Bad모나드가 될 수 없다고 주장합니다 .


부분 증명 :

다음 fmap을 충족시키는 정의 하는 방법은 하나뿐입니다 fmap id = id.

instance Functor Bad where
    fmap f (B x) = B $ fmap (fmap f) x

모나드 법칙을 상기하십시오.

(1) join (return x) = x
(2) join (fmap return x) = x
(3) join (join x) = join (fmap join x)

의 정의 return x에는 두 가지 선택이 있습니다. B Nothing또는 B (Just (P x x)). x(1)과 (2)에서 돌아올 희망을 가지려면 버릴 수 없으므로 x두 번째 옵션을 선택해야합니다.

return' :: a -> Bad a
return' x = B (Just (P x x))

그게 떠난다 join. 가능한 입력이 적기 때문에 각각에 대해 케이스를 만들 수 있습니다.

join :: Bad (Bad a) -> Bad a
(A) join (B Nothing) = ???
(B) join (B (Just (P (B Nothing)          (B Nothing))))          = ???
(C) join (B (Just (P (B (Just (P x1 x2))) (B Nothing))))          = ???
(D) join (B (Just (P (B Nothing)          (B (Just (P x1 x2)))))) = ???
(E) join (B (Just (P (B (Just (P x1 x2))) (B (Just (P x3 x4)))))) = ???

출력 형식을 갖기 때문에 Bad a, 유일한 옵션은 B Nothing또는 B (Just (P y1 y2))여기서 y1, y2로부터 선택되어야한다 x1 ... x4.

(A)와 (B)의 경우에는 type 값이 없으므로 두 경우 모두 a반환해야합니다 B Nothing.

사례 (E)는 (1) 및 (2) 모나드 법칙에 의해 결정됩니다.

-- apply (1) to (B (Just (P y1 y2)))
join (return' (B (Just (P y1 y2))))
= -- using our definition of return'
join (B (Just (P (B (Just (P y1 y2))) (B (Just (P y1 y2))))))
= -- from (1) this should equal
B (Just (P y1 y2))

반환하기 위해 B (Just (P y1 y2))케이스 (E)에서,이 방법은 우리가 선택해야 y1하나에서 x1x3, 그리고 y2에서 하나 x2또는 x4.

-- apply (2) to (B (Just (P y1 y2)))
join (fmap return' (B (Just (P y1 y2))))
= -- def of fmap
join (B (Just (P (return y1) (return y2))))
= -- def of return
join (B (Just (P (B (Just (P y1 y1))) (B (Just (P y2 y2))))))
= -- from (2) this should equal
B (Just (P y1 y2))

마찬가지로, 이것은 우리가 선택해야한다고 말한다 y1하나에서 x1x2, 그리고 y2에서 하나 x3또는 x4. 두 가지를 결합하여 (E)의 오른쪽이이어야 함을 결정합니다 B (Just (P x1 x4)).

지금까지는 모두 좋지만 (C)와 (D)의 오른쪽을 채우려 고 할 때 문제가 발생합니다.

각각에 대해 5 개의 가능한 오른편이 있으며 어떤 조합도 작동하지 않습니다. 아직 이에 대한 좋은 주장은 없지만 모든 조합을 철저히 테스트하는 프로그램이 있습니다.

{-# LANGUAGE ImpredicativeTypes, ScopedTypeVariables #-}

import Control.Monad (guard)

data Pair a = P a a
  deriving (Eq, Show)

instance Functor Pair where
  fmap f (P x y) = P (f x) (f y)

instance Monad Pair where
  return x = P x x
  P a b >>= f = P x y
    where P x _ = f a
          P _ y = f b

newtype Bad a = B (Maybe (Pair a))
  deriving (Eq, Show)

instance Functor Bad where
  fmap f (B x) = B $ fmap (fmap f) x

-- The only definition that could possibly work.
unit :: a -> Bad a
unit x = B (Just (P x x))

-- Number of possible definitions of join for this type. If this equals zero, no monad for you!
joins :: Integer
joins = sum $ do
  -- Try all possible ways of handling cases 3 and 4 in the definition of join below.
  let ways = [ \_ _ -> B Nothing
             , \a b -> B (Just (P a a))
             , \a b -> B (Just (P a b))
             , \a b -> B (Just (P b a))
             , \a b -> B (Just (P b b)) ] :: [forall a. a -> a -> Bad a]
  c3 :: forall a. a -> a -> Bad a <- ways
  c4 :: forall a. a -> a -> Bad a <- ways

  let join :: forall a. Bad (Bad a) -> Bad a
      join (B Nothing) = B Nothing -- no choice
      join (B (Just (P (B Nothing) (B Nothing)))) = B Nothing -- again, no choice
      join (B (Just (P (B (Just (P x1 x2))) (B Nothing)))) = c3 x1 x2
      join (B (Just (P (B Nothing) (B (Just (P x3 x4)))))) = c4 x3 x4
      join (B (Just (P (B (Just (P x1 x2))) (B (Just (P x3 x4)))))) = B (Just (P x1 x4)) -- derived from monad laws

  -- We've already learnt all we can from these two, but I decided to leave them in anyway.
  guard $ all (\x -> join (unit x) == x) bad1
  guard $ all (\x -> join (fmap unit x) == x) bad1

  -- This is the one that matters
  guard $ all (\x -> join (join x) == join (fmap join x)) bad3

  return 1

main = putStrLn $ show joins ++ " combinations work."

-- Functions for making all the different forms of Bad values containing distinct Ints.

bad1 :: [Bad Int]
bad1 = map fst (bad1' 1)

bad3 :: [Bad (Bad (Bad Int))]
bad3 = map fst (bad3' 1)

bad1' :: Int -> [(Bad Int, Int)]
bad1' n = [(B Nothing, n), (B (Just (P n (n+1))), n+2)]

bad2' :: Int -> [(Bad (Bad Int), Int)]
bad2' n = (B Nothing, n) : do
  (x, n')  <- bad1' n
  (y, n'') <- bad1' n'
  return (B (Just (P x y)), n'')

bad3' :: Int -> [(Bad (Bad (Bad Int)), Int)]
bad3' n = (B Nothing, n) : do
  (x, n')  <- bad2' n
  (y, n'') <- bad2' n'
  return (B (Just (P x y)), n'')


답변

작은 구체적인 반례의 경우 터미널 모나드를 고려하십시오.

data Thud x = Thud

return>>=바로 가서 Thud하고, 법은 하찮게십시오.

이제 Bool에 대한 라이터 모나드 (예를 들어 xor-monoid 구조)도 만들어 보겠습니다.

data Flip x = Flip Bool x

instance Monad Flip where
   return x = Flip False x
   Flip False x  >>= f = f x
   Flip True x   >>= f = Flip (not b) y where Flip b y = f x

어, 음, 작곡이 필요해

newtype (:.:) f g x = C (f (g x))

이제 정의 해보세요 …

instance Monad (Flip :.: Thud) where  -- that's effectively the constant `Bool` functor
  return x = C (Flip ??? Thud)
  ...

Parametricity는 ???에 어떤 유용한 방식으로도 의존 할 수 없으므로 x상수 여야합니다. 결과적으로 join . return반드시 일정한 기능이므로 법칙은

join . return = id

정의가 무엇이든 실패해야 join하며 return우리는 선택합니다.


답변

제외 된 중간 구성

(->) r모든위한 모나드입니다 rEither e모든위한 모나드이다 e. 구성 ( (->) r내부, Either e외부)을 정의 해 보겠습니다 .

import Control.Monad
newtype Comp r e a = Comp { uncomp :: Either e (r -> a) }

내가 주장 하는 경우 Comp r e모든위한 모나드했다 r그리고 e우리가 깨달을 수 exluded 중간의 법을 . 이것은 기능 언어의 유형 시스템의 기초가되는 직관적 논리 에서는 불가능 합니다 (중간 제외의 법칙을 갖는 것은 call / cc 연산자 를 갖는 것과 동일합니다 ).

Comp모나드 라고 가정 해 봅시다 . 그런 다음 우리는

join :: Comp r e (Comp r e a) -> Comp r e a

그래서 우리는 정의 할 수 있습니다

swap :: (r -> Either e a) -> Either e (r -> a)
swap = uncomp . join . Comp . return . liftM (Comp . liftM return)

(이것은 Brent가 언급 swap한 문서 작성 모나드 , 섹션 4.3의 기능 일 뿐이며 newtype의 (de) 생성자가 추가 된 경우에만 해당됩니다. 어떤 속성이 있는지는 신경 쓰지 않습니다. 중요한 것은 정의 가능하고 총체적이라는 것입니다. .)

이제 설정하겠습니다

data False -- an empty datatype corresponding to logical false
type Neg a = (a -> False) -- corresponds to logical negation

r = b,, e = b에 대한 스왑 전문화 a = False:

excludedMiddle :: Either b (Neg b)
excludedMiddle = swap Left

결론 :(->) rEither r모나드 임에도 불구 하고 그 구성 Comp r r은 그럴 수 없습니다.

참고 : 이는 정의 방법 ReaderTEitherT정의 에도 반영 됩니다. 모두 ReaderT r (Either e)EitherT e (Reader r)동형이다 r -> Either e a! dual에 대한 모나드를 정의하는 방법은 없습니다 Either e (r -> a).


탈출 IO행동

같은 맥락에서 어떤 식 으로든 IO탈출로 이어지는 많은 예가 있습니다 IO. 예를 들면 :

newtype Comp r a = Comp { uncomp :: IO (r -> a) }

swap :: (r -> IO a) -> IO (r -> a)
swap = uncomp . join . Comp . return . liftM (Comp . liftM return)

이제 가자

main :: IO ()
main = do
   let foo True  = print "First" >> return 1
       foo False = print "Second" >> return 2
   f <- swap foo
   input <- readLn
   print (f input)

이 프로그램이 실행되면 어떻게됩니까? 두 가지 가능성이 있습니다.

  1. 콘솔에서 읽은 “First”또는 “Second”가 인쇄 input됩니다. 즉, 일련의 작업이 반전 되었고 해당 작업이 foopure로 이스케이프 되었음을 의미합니다 f.
  2. 또는 swap(따라서 join) IO조치를 버리고 “First”또는 “Second”가 인쇄되지 않습니다. 그러나 이것은 join법 을 위반 한다는 것을 의미합니다

    join . return = id
    

    경우 때문에 join던져 IO멀리 조치를 한 후

    foo ≠ (join . return) foo
    

다른 유사한 IO+ 모나드 조합으로 인해

swapEither :: IO (Either e a) -> Either e (IO a)
swapWriter :: (Monoid e) => IO (Writer e a) -> Writer e (IO a)
swapState  :: IO (State e a) -> State e (IO a)
...

그들의 join구현은 e탈출을 허용 IO하거나 그것을 버리고 다른 것으로 대체하여 법을 위반해야합니다.


답변

링크에서이 데이터 유형을 참조하므로 특정 구현을 선택해 보겠습니다. data A3 a = A3 (A1 (A2 a))

나는 임의로 선택할 A1 = IO, A2 = []것이다. 우리는 또한 그것을 a로 만들고 newtype재미를 위해 특별히 뾰족한 이름을 줄 것입니다.

newtype ListT IO a = ListT (IO [a])

해당 유형에서 임의의 작업을 생각해 내고 서로 다르지만 동일한 두 가지 방법으로 실행 해 보겠습니다.

λ> let v n = ListT $ do {putStr (show n); return [0, 1]}
λ> runListT $ ((v >=> v) >=> v) 0
0010101[0,1,0,1,0,1,0,1]
λ> runListT $ (v >=> (v >=> v)) 0
0001101[0,1,0,1,0,1,0,1]

보시다시피 이것은 연관성 법칙을 위반합니다 : ∀x y z. (x >=> y) >=> z == x >=> (y >=> z).

그것은 밝혀 ListT m경우에만 모나드 인 mA는 교환 법칙이 성립 모나드. 이것은 []“두 개의 임의의 모나드를 구성하면 모나드를 생성한다”는 보편적 인 규칙을 깨뜨리는 큰 범주의 모나드가로 구성되는 것을 방지합니다 .

참조 : https://stackoverflow.com/a/12617918/1769569


답변

모나드는 구성하지 않습니다. 일반적인 방법이 아닙니다. 모나드를 구성하는 일반적인 방법이 없습니다. 참조 https://www.slideshare.net/pjschwarz/monads-do-not-compose를


답변