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


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하나에서 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'')

감사합니다, 확신합니다! 증거를 단순화하는 방법이 있는지 궁금합니다.
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모든위한 모나드입니다 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하거나 그것을 버리고 다른 것으로 대체하여 법을 위반해야합니다.


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