Простой пример, показывающий, что IO не удовлетворяет законам монады?

Я уже упоминал, что IO не удовлетворяет законам монады, но я не нашел простого примера, показывающего это. Кто-нибудь знает пример? Спасибо.

Изменить: Как ertes и n.m. указал, что использование seq несколько незаконно, поскольку оно может заставить любую монаду нарушить законы (в сочетании с undefined). Так как undefined можно рассматривать как непереходное вычисление, он отлично подходит для его использования.

Итак, пересмотренный вопрос: Кто-нибудь знает пример, показывающий, что IO не удовлетворяет законам монады, не используя seq? (или, возможно, доказательство того, что IO законов, если seq не допускается?)

Ответ 1

tl; dr upfront: seq - единственный способ.

Поскольку реализация IO не предписывается стандартом, мы можем рассматривать только конкретные реализации. Если мы посмотрим на реализацию GHC, поскольку она доступна из источника (возможно, что некоторые из закулисных специальных процедур IO вводят нарушения законов монады, но я не знаю о таком возникновении),

-- in GHC.Types (ghc-prim)
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

-- in GHC.Base (base)
instance  Monad IO  where
    {-# INLINE return #-}
    {-# INLINE (>>)   #-}
    {-# INLINE (>>=)  #-}
    m >> k    = m >>= \ _ -> k
    return    = returnIO
    (>>=)     = bindIO
    fail s    = failIO s

returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)

bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s

thenIO :: IO a -> IO b -> IO b
thenIO (IO m) k = IO $ \ s -> case m s of (# new_s, _ #) -> unIO k new_s

unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a

он реализован как (строгая) государственная монада. Таким образом, любое нарушение законов монады IO, также производится Control.Monad.State[.Strict].

Посмотрите на законы монады и посмотрите, что происходит в IO:

return x >>= f ≡ f x:
return x >>= f = IO $ \s -> case (\t -> (# t, x #)) s of
                              (# new_s, a #) -> unIO (f a) new_s
               = IO $ \s -> case (# s, x #) of
                              (# new_s, a #) -> unIO (f a) new_s
               = IO $ \s -> unIO (f x) s

Игнорирование обертки newtype означает, что return x >>= f становится \s -> (f x) s. Единственный способ (возможно) отличить от f x - seq. (И seq может отличить его только, если f x ≡ undefined.)

m >>= return ≡ m:
(IO k) >>= return = IO $ \s -> case k s of
                                 (# new_s, a #) -> unIO (return a) new_s
                  = IO $ \s -> case k s of
                                 (# new_s, a #) -> (\t -> (# t, a #)) new_s
                  = IO $ \s -> case k s of
                                 (# new_s, a #) -> (# new_s, a #)
                  = IO $ \s -> k s

снова игнорируя новую оболочку newtype, k заменяется на \s -> k s, что опять же различимо только на seq, и только если k ≡ undefined.

m >>= (\x -> g x >>= h) ≡ (m >>= g) >>= h:
(IO k) >>= (\x -> g x >>= h) = IO $ \s -> case k s of
                                            (# new_s, a #) -> unIO ((\x -> g x >>= h) a) new_s
                             = IO $ \s -> case k s of
                                            (# new_s, a #) -> unIO (g a >>= h) new_s
                             = IO $ \s -> case k s of
                                            (# new_s, a #) -> (\t -> case unIO (g a) t of
                                                                       (# new_t, b #) -> unIO (h b) new_t) new_s
                             = IO $ \s -> case k s of
                                            (# new_s, a #) -> case unIO (g a) new_s of
                                                                (# new_t, b #) -> unIO (h b) new_t
((IO k) >>= g) >>= h = IO $ \s -> case (\t -> case k t of
                                                (# new_s, a #) -> unIO (g a) new_s) s of
                                    (# new_t, b #) -> unIO (h b) new_t
                     = IO $ \s -> case (case k s of
                                          (# new_s, a #) -> unIO (g a) new_s) of
                                    (# new_t, b #) -> unIO (h b) new_t

Теперь мы обычно имеем

case (case e of                    case e of
        pat1 -> ex1) of       ≡      pat1 -> case ex1 of
  pat2 -> ex2                                  pat2 -> ex2

за уравнение 3.17.3. (a) языкового отчета, поэтому этот закон содержит не только modulo seq.

Подводя итоги, IO удовлетворяет законам монады, за исключением того, что seq может различать undefined и \s -> undefined s. То же самое верно для State[T], Reader[T], (->) a и любых других монадов, обертывающих тип функции.

Ответ 2

Все монады в Haskell являются только монадами, если вы исключаете странный комбинатор seq. Это справедливо и для IO. Поскольку seq на самом деле не является обычной функцией, но включает магию, вы должны исключить ее из проверки законов монады по той же причине, которую вы должны исключить unsafePerformIO. Используя seq, вы можете доказать, что все монады ошибочны, как показано ниже.

В категории Клейсли монада порождает: return - тождественный морфизм, а (<=<) - состав. Итак, return должно быть тождеством для (<=<):

return <=< x = x

Использование seq даже Identity и, возможно, не может быть монадами:

seq (return <=< undefined :: a -> Identity b) () = ()
seq (undefined            :: a -> Identity b) () = undefined

seq (return <=< undefined :: a -> Maybe b) () = ()
seq (undefined            :: a -> Maybe b) () = undefined

Ответ 3

Одним из законов монады является то, что

m >>= return ≡ m

Попробуй попробовать в GHCi:

Prelude> seq ( undefined >>= return :: IO () ) "hello, world"
"hello, world"

Prelude> seq ( undefined :: IO () ) "hello, world"
*** Exception: Prelude.undefined

Итак, undefined >>= return не совпадает с undefined, поэтому IO не является монадой. Если мы попробуем то же самое для монады Maybe, с другой стороны:

Prelude> seq ( undefined >>= return :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined

Prelude> seq ( undefined :: Maybe () ) "hello, world"
*** Exception: Prelude.undefined    

Два выражения идентичны - поэтому Maybe не исключается из монады в этом примере.

Если у кого-то есть пример, который не полагается на использование seq или undefined, мне было бы интересно увидеть его.

Ответ 4

m >>= return ≡ m

:

sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: IO ()

блокирует память и увеличивает время вычисления, а

sequence_ $ take 100000 $ iterate (>>=return) (return ()) :: Maybe ()

нет.

AFAIR существует Monad Transformer, который решает эту проблему; если я правильно понял, это Трансформатор Монады Codensity.