Ví dụ cụ thể cho thấy các đơn nguyên không đóng theo thành phần (có bằng chứng)?


82

Ai cũng biết rằng các chức năng ứng dụng được đóng lại trong thành phần nhưng các đơn nguyên thì không. Tuy nhiên, tôi đã gặp khó khăn khi tìm một ví dụ đối chiếu cụ thể cho thấy rằng không phải lúc nào các monads cũng sáng tác.

Câu trả lời này đưa ra [String -> a]như một ví dụ về một đơn nguyên. Sau khi chơi với nó một chút, tôi tin điều đó bằng trực giác, nhưng câu trả lời đó chỉ nói rằng "không thể thực hiện tham gia" mà không thực sự đưa ra bất kỳ lý do nào. Tôi muốn một cái gì đó trang trọng hơn. Tất nhiên có rất nhiều chức năng với loại [String -> [String -> a]] -> [String -> a]; người ta phải chỉ ra rằng bất kỳ chức năng nào như vậy nhất thiết không thỏa mãn các luật đơn nguyên.

Bất kỳ ví dụ nào (với bằng chứng kèm theo) sẽ làm; Tôi không nhất thiết phải tìm kiếm một bằng chứng cụ thể cho ví dụ trên.


Gần nhất tôi có thể tìm thấy là phụ lục của web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf , cho thấy rằng theo rất nhiều giả định đơn giản hóa, không thể viết joincho thành phần của hai monads trong nói chung . Nhưng điều này không dẫn đến bất kỳ ví dụ cụ thể nào .
Brent Yorgey

Bạn có thể nhận được câu trả lời tốt hơn cho câu hỏi này trên cs.stackexchange.com, trang web Exchange Science Stack mới.
Patrick87

3
Có lẽ tôi không hiểu, nhưng tôi nghĩ câu hỏi có thể được định nghĩa chính xác hơn. Khi bạn nói "soạn thảo" hai monads, có phải ý bạn chỉ đơn giản là tạo các hàm tạo kiểu? Và khi kết quả "không phải là một đơn nguyên", điều này có nghĩa là một thể hiện đơn nguyên của bộ tạo kiểu đó không thể được viết? Và, nếu một thể hiện đơn nguyên cho phương thức khởi tạo kiểu soạn có thể được viết, thì nó có phải chịu bất kỳ mối quan hệ nào với các thể hiện của hai đơn nguyên nhân tố không, hay nó có thể hoàn toàn không liên quan?
Owen

1
Vâng, ý tôi là soạn các hàm tạo kiểu; "not a monad" có nghĩa là không thể viết một trường hợp monad hợp lệ (hợp pháp); và tôi không quan tâm liệu trường hợp cho bố cục có bất kỳ mối liên hệ nào với các trường hợp của các yếu tố hay không.
Brent Yorgey

Câu trả lời:


42

Hãy xem xét đơn nguyên này là đồng cấu với (Bool ->)đơn nguyên:

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

và soạn nó với Maybemonad:

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

Tôi khẳng định đó Badkhông thể là đơn nguyên.


Chứng minh một phần:

Chỉ có một cách để xác định fmapđiều đó thỏa mãn fmap id = id:

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

Nhắc lại các luật đơn nguyên:

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

Đối với định nghĩa của return x, chúng ta có hai lựa chọn: B Nothinghoặc B (Just (P x x)). Rõ ràng là để có hy vọng quay trở lại xtừ (1) và (2), chúng ta không thể vứt bỏ x, vì vậy chúng ta phải chọn phương án thứ hai.

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

Rời đi join. Vì chỉ có một số đầu vào khả thi, chúng tôi có thể đưa ra một trường hợp cho mỗi:

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)))))) = ???

Vì đầu ra có loại Bad a, các tùy chọn duy nhất là B Nothinghoặc B (Just (P y1 y2))ở đâu y1, y2phải được chọn từ x1 ... x4.

Trong trường hợp (A) và (B), chúng tôi không có giá trị kiểu nào a, vì vậy chúng tôi buộc phải trả về B Nothingtrong cả hai trường hợp.

Trường hợp (E) được xác định bởi (1) và (2) luật đơn nguyên:

-- 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))

Để trả về B (Just (P y1 y2))trong trường hợp (E), điều này có nghĩa là chúng ta phải chọn y1một trong hai x1hoặc x3, và y2từ một trong hai x2hoặc 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))

Tương tự như vậy, điều này nói rằng chúng ta phải chọn y1từ một trong hai x1hoặc x2, và y2từ một trong hai x3hoặc x4. Kết hợp cả hai, chúng tôi xác định rằng phía bên phải của (E) phải là B (Just (P x1 x4)).

Cho đến nay, tất cả đều tốt, nhưng vấn đề xảy ra khi bạn cố gắng điền vào các cạnh bên phải cho (C) và (D).

Có 5 bên tay phải có thể có cho mỗi bên và không tổ hợp nào hoạt động. Tôi chưa có lập luận tốt cho điều này, nhưng tôi có một chương trình kiểm tra toàn diện tất cả các kết hợp:

{-# 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'')

Cảm ơn, tôi bị thuyết phục! Mặc dù điều đó khiến tôi tự hỏi liệu có cách nào để đơn giản hóa việc chứng minh của bạn không.
Brent Yorgey

1
@BrentYorgey: Tôi nghi ngờ là nên có, vì các vấn đề với trường hợp (C) và (D) dường như rất giống với các vấn đề bạn gặp phải khi cố gắng xác định swap :: Pair (Maybe a) -> Maybe (Pair a).
hammar

11
Vì vậy, tóm lại: các monads được phép loại bỏ thông tin, và điều đó không sao nếu chúng chỉ lồng vào nhau. Nhưng khi bạn có một đơn nguyên lưu giữ thông tin và một đơn nguyên giảm thông tin, hãy kết hợp hai đơn nguyên loại bỏ thông tin, mặc dù đơn nguyên lưu giữ thông tin cần thông tin đó được lưu giữ để đáp ứng luật của đơn nguyên riêng. Vì vậy, bạn không thể kết hợp các monads tùy ý. (Đây là lý do tại sao bạn cần các monads có thể Traversable, điều này đảm bảo rằng chúng sẽ không làm mất thông tin liên quan; chúng có thể kết hợp tùy ý.) Cảm ơn vì trực giác!
Xanthir

@Xanthir Việc sáng tác chỉ hoạt động theo một thứ tự: (Maybe a, Maybe a)là một đơn nguyên (vì nó là sản phẩm của hai Maybe (a, a)đơn nguyên ) nhưng không phải là một đơn nguyên. Tôi cũng đã xác minh rằng đó Maybe (a,a)không phải là đơn nguyên bằng các tính toán rõ ràng.
winitzki

Tâm cho thấy tại sao Maybe (a, a)không phải là đơn nguyên? Cả Có thể và Tuple đều có thể chuyển đổi, và nên được kết hợp theo bất kỳ thứ tự nào; có những câu hỏi SO khác cũng nói về ví dụ cụ thể này.
Xanthir

38

Đối với một mẫu đối chiếu bê tông nhỏ, hãy xem xét đơn nguyên cuối.

data Thud x = Thud

Các return>>=chỉ cần đi Thud, và pháp luật giữ trivially.

Bây giờ chúng ta cũng có đơn nguyên viết cho Bool (với, giả sử, cấu trúc 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

Ờ, ừm, chúng ta sẽ cần bố cục

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

Bây giờ hãy thử xác định ...

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

Tham số cho chúng ta biết rằng ???không thể phụ thuộc vào bất kỳ cách hữu ích nào vàox , vì vậy nó phải là một hằng số. Kết quả là, join . returncũng nhất thiết phải là một hàm hằng, do đó luật

join . return = id

phải thất bại cho bất kỳ định nghĩa nào joinreturnchúng tôi chọn.


3
Trên Hamalainen Carlo của blog có bổ sung, rất rõ ràng và phân tích chi tiết của câu trả lời ở trên mà tôi đã tìm thấy hữu ích: carlo-hamalainen.net/blog/2014/1/2/...
paluh

34

Xây dựng trung gian bị loại trừ

(->) rlà đơn nguyên cho mọi rEither elà đơn nguyên cho mọi e. Hãy xác định thành phần của chúng ( (->) rbên trong, Either ebên ngoài):

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

Tôi khẳng định rằng nếu Comp r elà một đơn nguyên cho mọi rethì chúng ta có thể nhận ra quy luật trung gian bị loại trừ . Điều này không thể xảy ra trong logic trực giác làm nền tảng cho các hệ thống kiểu chữ của ngôn ngữ chức năng (có luật loại trừ ở giữa tương đương với việc có lệnh gọi / cc toán tử ).

Giả sử Complà một đơn nguyên. Sau đó chúng tôi có

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

và vì vậy chúng tôi có thể xác định

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

(Đây chỉ là swapchức năng từ monads Soạn thảo giấy mà Brent đề cập, Sect. 4.3, chỉ với các hàm tạo của newtype's (de) được thêm vào. Lưu ý rằng chúng tôi không quan tâm nó có thuộc tính gì, điều quan trọng duy nhất là nó có thể xác định được và tổng số .)

Bây giờ chúng ta hãy thiết lập

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

và chuyên hoán đổi cho r = b, e = b, a = False:

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

Kết luận: Mặc dù (->) rEither rlà đơn nguyên nhưng thành phần của chúng Comp r rkhông thể có.

Lưu ý: Điều này cũng được phản ánh trong cách thức ReaderTEitherTđược xác định. Cả hai ReaderT r (Either e)EitherT e (Reader r)đều là đồng phân với r -> Either e a! Không có cách nào làm thế nào để xác định đơn nguyên cho kép Either e (r -> a).


IOHành động trốn thoát

Có rất nhiều ví dụ trong cùng một tĩnh mạch liên quan IOvà dẫn đến việc thoát ra ngoài IObằng cách nào đó. Ví dụ:

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)

Bây giờ chúng ta hãy có

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)

Điều gì sẽ xảy ra khi chương trình này được chạy? Có hai khả năng:

  1. "Đầu tiên" hoặc "Thứ hai" được in sau khi chúng tôi đọc inputtừ bảng điều khiển, có nghĩa là chuỗi hành động đã được đảo ngược và rằng các hành động từ foothoát trở thành thuần túy f.
  2. Hoặc swap(do đó join) loại bỏ IOhành động và không bao giờ "Đầu tiên" và "Thứ hai" được in. Nhưng điều này có nghĩa là joinvi phạm pháp luật

    join . return = id
    

    bởi vì nếu joinném IOhành động đi, thì

    foo ≠ (join . return) foo
    

Các IOkết hợp tương tự + đơn nguyên khác dẫn đến xây dựng

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)
...

Việc jointriển khai của họ phải cho phép ethoát khỏi IOhoặc họ phải vứt bỏ nó và thay thế bằng thứ khác, vi phạm pháp luật.


(Tôi cho rằng "ap" là lỗi đánh máy "trong đó fmap, pure và ap là các định nghĩa chính tắc" (nên được <*>thay thế), đã cố gắng chỉnh sửa nó nhưng tôi được thông báo rằng bản chỉnh sửa của mình quá ngắn.) --- Không rõ tôi rằng có một định nghĩa cho joinngụ ý một định nghĩa cho swap. Bạn có thể mở rộng về nó? Giấy giới thiệu bởi Brent dường như ngụ ý rằng để đi từ joinđể swapchúng ta cần các giả định sau: joinM . fmapM join = join . joinMjoin . fmap (fmapM joinN ) = fmapM joinN . join nơi joinM = tham gia :: M vv
Rafael Caetano

1
@RafaelCaetano Cảm ơn vì lỗi đánh máy, tôi đã sửa nó (và cũng đã đổi tên hàm swapcho phù hợp với giấy). Tôi đã không kiểm tra giấy cho đến bây giờ, và bạn nói đúng, có vẻ như chúng ta cần J (1) và J (2) để xác định swap<-> join. Đây có lẽ là một điểm yếu trong bằng chứng của tôi và tôi sẽ suy nghĩ về nó nhiều hơn (có lẽ có thể có được điều gì đó từ thực tế là nó Applicative).
Petr

@RafaelCaetano Nhưng tôi tin rằng bằng chứng vẫn hợp lệ: Nếu có join, chúng tôi có thể xác định swap :: (Int -> Maybe a) -> Maybe (Int -> a)bằng cách sử dụng định nghĩa trên (bất kể điều này swapthỏa mãn luật nào ). Làm thế nào sẽ swapcư xử như vậy ? Không có Int, nó không có gì để chuyển đến đối số của nó, vì vậy nó sẽ phải trả về Nothingcho tất cả các đầu vào. Tôi tin rằng chúng ta có thể nhận được sự mâu thuẫn đối với joincác luật đơn nguyên mà không cần phải xác định jointừ swapphía sau. Tôi sẽ kiểm tra nó và cho bạn biết.
Petr

@Petr: Tôi đồng ý với Rafael rằng đây không phải là bằng chứng hoàn toàn mà tôi đang tìm kiếm, nhưng tôi cũng tò mò muốn biết liệu nó có thể được sửa theo những dòng bạn đề cập hay không.
Brent Yorgey

1
@ PetrPudlák Chà, rất hay! Vâng, tôi hoàn toàn mua nó ngay bây giờ. Đây là một số hiểu biết thực sự thú vị. Tôi sẽ không thể ngờ rằng chỉ đơn giản là có thể xây dựng hoán đổi có thể dẫn đến một sự mâu thuẫn, mà không cần tham chiếu đến bất kỳ luật đơn nguyên nào cả! Nếu tôi có thể chấp nhận nhiều câu trả lời, tôi cũng sẽ chấp nhận câu trả lời này.
Brent Yorgey

4

Liên kết của bạn tham chiếu đến loại dữ liệu này, vì vậy hãy thử chọn một số triển khai cụ thể: data A3 a = A3 (A1 (A2 a))

Tôi sẽ tùy ý hái A1 = IO, A2 = []. Chúng tôi cũng sẽ đặt nó newtypevà đặt cho nó một cái tên đặc biệt rõ ràng, cho vui:

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

Hãy đưa ra một số hành động tùy ý trong loại đó và chạy nó theo hai cách khác nhau nhưng bình đẳng:

λ> 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]

Như bạn có thể thấy, điều này phá vỡ luật kết hợp: ∀x y z. (x >=> y) >=> z == x >=> (y >=> z) .

Hóa ra, ListT mchỉ là một đơn nguyên nếu mlà một đơn nguyên giao hoán . Điều này ngăn không cho một lượng lớn các đơn nguyên soạn thảo với[] đơn nguyên , điều này phá vỡ quy tắc phổ biến về "việc soạn hai đơn nguyên tùy ý tạo ra một đơn nguyên".

Xem thêm: https://stackoverflow.com/a/12617918/1769569


11
Tôi nghĩ rằng điều này chỉ cho thấy rằng một định nghĩa cụ thể về ListTkhông tạo ra một đơn nguyên trong mọi trường hợp, hơn là cho thấy rằng không có định nghĩa khả thi nào có thể hoạt động.
CA McCann

Tôi không cần phải làm vậy. Phủ định của "cho tất cả cái này, cái kia" là "tồn tại một ví dụ phản bác". Câu hỏi được đặt ra là "đối với tất cả các đơn nguyên, thành phần của chúng tạo thành một đơn nguyên". Tôi đã chỉ ra sự kết hợp của các loại là đơn nguyên của riêng chúng, nhưng không thể sáng tác.
hpc

11
@hpc, nhưng thành phần của hai đơn nguyên nhiều hơn thành phần của các loại của chúng. Bạn cũng cần các phép toán, và cách giải thích của tôi về câu hỏi của Brent là có thể không có cách nào có phương pháp để xác định việc thực hiện các phép toán - anh ấy đang tìm kiếm một thứ gì đó mạnh mẽ hơn, rằng một số tác phẩm không có phép toán nào thỏa mãn các luật, liệu có thể dẫn xuất một cách máy móc hay không. Điều đó có ý nghĩa?
luqui 23/10/12

Vâng, luqui đã đúng. Xin lỗi nếu câu hỏi ban đầu của tôi không rõ ràng.
Brent Yorgey

Điều thực sự còn thiếu trong câu trả lời này là Monadví dụ ListTvà một minh chứng rằng không có bất kỳ câu trả lời nào khác. Những tuyên bố là "cho tất cả điều này, tồn tại mà" và do đó phủ định là "có tồn tại này như vậy mà cho tất cả những gì"
Ben Millwood

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.