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


82

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

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

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


내가 찾을 수있는 가장 가까운 것은 web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf 의 부록으로 , 많은 단순화 가정 하에서 join두 개의 모나드 구성에 대해 작성하는 것이 불가능 함을 보여줍니다 . 일반 . 그러나 이것은 구체적인 예로 이어지지 않습니다 .
— Brent Yorgey

새로운 Computer Science Stack Exchange 사이트 인 cs.stackexchange.com에서이 질문에 대한 더 나은 답변을 얻을 수 있습니다.
— Patrick87

3
이해가 안되지만 질문이 더 정확하게 정의 될 수 있다고 생각합니다. 두 모나드를 "구성"한다고하면 단순히 유형 생성자를 구성한다는 의미입니까? 그리고 결과가 "모나드가 아님"이라면 해당 유형의 모나드 인스턴스를 작성할 수 없다는 의미입니까? 그리고 만약 구성된 타입 생성자에 대한 모나드 인스턴스가 작성 될 수 있다면, 두 팩터 모나드의 인스턴스와 어떤 관계를 가져야합니까, 아니면 완전히 관련이 없을 수 있습니까?
— Owen

1
예, 유형 생성자를 구성하는 것을 의미합니다. "모나드가 아님"은 유효한 (합법적 인) 모나드 인스턴스를 작성할 수 없음을 의미합니다. 컴포지션의 인스턴스가 요인의 인스턴스와 관련이 있는지 여부는 신경 쓰지 않습니다.
— Brent Yorgey

답변:


42

모나드와 동형 인 다음 모나드를 고려하십시오 (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하나에서 x1나 x3, 그리고 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하나에서 x1나 x2, 그리고 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'')

감사합니다, 확신합니다! 증거를 단순화하는 방법이 있는지 궁금합니다.
— Brent Yorgey

1
@BrentYorgey : 사례 (C) 및 (D)의 문제가 정의하려고 할 때 발생하는 문제와 매우 유사한 것처럼 보이기 때문에 있어야한다고 생각합니다 swap :: Pair (Maybe a) -> Maybe (Pair a).
— hammar

11
간단히 말해, 모나드는 정보를 버릴 수 있으며, 그 자체로 중첩되어 있어도 괜찮습니다. 그러나 정보 보존 모나드와 정보 삭제 모나드가있는 경우 정보 보존 모나드 가 자체 모나드 법칙을 충족하기 위해 해당 정보를 유지 해야 하지만 두 방울 정보를 결합합니다 . 따라서 임의의 모나드를 결합 할 수 없습니다. (이것이 관련 정보를 삭제하지 않도록 보장하는 Traversable 모나드가 필요한 이유입니다. 임의로 구성 할 수 있습니다.) 직감에 감사드립니다!
— Xanthir

@Xanthir Composing은 하나의 순서로만 작동합니다 (Maybe a, Maybe a). 모나드 (두 모나드의 곱이기 때문에)이지만 모나드 Maybe (a, a)는 아닙니다. 또한 Maybe (a,a)명시적인 계산을 통해 모나드가 아님을 확인했습니다 .
— winitzki

Maybe (a, a)모나드가 아닌 이유를 보여주는 마음 ? Maybe와 Tuple은 모두 순회 가능하며 어떤 순서로든 구성 할 수 있어야합니다. 이 특정 예에 대해 이야기하는 다른 SO 질문도 있습니다.
— Xanthir

38

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

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우리는 선택합니다.


3
Carlo의 Hamalainen 블로그에는 위의 답변에 대한 매우 명확하고 상세한 분석이 추가로 나와 있으며 도움이되었습니다. carlo-hamalainen.net/blog/2014/1/2/…
— paluh

34

제외 된 중간 구성

(->) r모든위한 모나드입니다 r및 Either 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

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

참고 : 이는 정의 방법 ReaderT과 EitherT정의 에도 반영 됩니다. 모두 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하거나 그것을 버리고 다른 것으로 대체하여 법을 위반해야합니다.


( "ap"이 "fmap, pure 및 ap가 표준 정의 인 곳"( <*>대신 있어야 함 ) 의 오타라고 생각 하고 편집하려고했지만 편집이 너무 짧다고 들었습니다.) --- 명확하지 않습니다. 에 대한 정의가 있다는 join것은 swap. 확장 할 수 있습니까? 브렌트 의해 언급 된 논문에서 갈 것을 암시하는 것 join에 swap우리는 다음과 같은 가정을 필요 joinM . fmapM join = join . joinM하고 join . fmap (fmapM joinN ) = fmapM joinN . join 어디에 joinM = 가입 :: 등 M,
— 라파엘 CAETANO

1
@RafaelCaetano 오타를 주셔서 감사합니다. 수정했습니다 (또한 swap논문과 일치하도록 기능 이름도 변경했습니다 ). 나는 지금까지 논문을 확인하지 않았고 당신이 맞습니다 . swap<-> 를 정의하려면 J (1)과 J (2)가 필요합니다 join. 이것은 아마도 내 증거의 약점이며 나는 그것에 대해 더 많이 생각할 것입니다 (아마도 그것이 사실이라는 사실에서 무언가를 얻을 수있을 것입니다 Applicative).
— Petr

@RafaelCaetano 그러나 나는 증명이 여전히 유효하다고 생각합니다 : 만약 우리가했다면 join, 우리 swap :: (Int -> Maybe a) -> Maybe (Int -> a)는 위의 정의를 사용하여 정의 할 수 있습니다 ( 이가 swap만족 하는 법칙에 관계없이 ). 그러한 swap행동 은 어떻게 될까요? no Int이면 인수에 전달할 것이 없으므로 Nothing모든 입력에 대해 반환 해야합니다. 나는 우리가 모순 얻을 수 있다고 join정의 할 필요없이의 모나드 법률 join에서 swap다시. 확인하고 알려 드리겠습니다.
— Petr

@Petr :이게 제가 찾고있는 증거가 아니라는 라파엘의 의견에 동의합니다.하지만 당신이 언급 한 라인을 따라 고칠 수 있는지도 궁금합니다.
— Brent Yorgey

1
@ PetrPudlák 와우, 정말 좋아요! 네, 지금 완전히 사요. 이것들은 정말 흥미로운 통찰력입니다. 나는 단순히 스왑 을 구성 할 수 있다는 것이 모나드 법칙을 전혀 참조하지 않고 모순으로 이어질 수 있다고 생각하지 않았을 것입니다 ! 여러 답변을 받아 들일 수 있다면 이것도 받아 들일 것입니다.
— Brent Yorgey

4

링크에서이 데이터 유형을 참조하므로 특정 구현을 선택해 보겠습니다. 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


11
나는 이것이 가능한 정의 가 작동 하지 않는다는ListT 것을 보여주는 것이 아니라 모든 경우에 하나의 특정 정의가 모나드를 생성하지 못한다 는 것을 보여줄 뿐이라고 생각합니다 .
— CA McCann

그럴 필요가 없습니다. "이 모든 것에 대해"의 부정은 "반대 사례가있다"입니다. 질문은 "모든 모나드에 대해 구성이 모나드를 형성합니다"였습니다. 자체적으로 모나드 인 유형의 조합을 보여 주었지만 작성할 수는 없습니다.
— hpc 2012-10-23

11
@hpc, 그러나 두 모나드의 구성은 해당 유형의 구성 이상입니다. 당신은 또한 작업이 필요하고 Brent의 질문에 대한 나의 해석은 작업의 구현을 도출하는 체계적인 방법이 없을 수 있다는 것입니다. 그는 더 강력한 것을 찾고 있습니다. 일부 구성에는 법을 충족시키는 작업 이 없습니다. 기계적으로 유도 할 수 있는지 여부. 말이 돼?
— luqui

네, luqui가 맞습니다. 내 원래 질문이 명확하지 않은 경우 죄송합니다.
— Brent Yorgey

이 답변에서 실제로 누락 된 것은에 대한 Monad인스턴스 ListT와 다른 항목이 없다는 데모입니다. 성명은 부정이 있도록 "이 모든 내용은 해당 존재"이며 "이 같은 존재하는 모든 것을"
— 벤 밀우드

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.