当我引入一个使用 Monad 作为参数的新参数时,为什么函数之间有效的 monadic 链接会中断?

Why does a working monadic linkage between functions break, when I introduce a new argument that uses the Monad as a parameter?

以下代码:

-- tst2.hs - showing successful monadic linkage.

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE FlexibleContexts #-}

import Protolude
import Control.Monad.Extra  (unfoldM)

foo :: Monad m
    => Int
    -> m [[Int]]
foo n = evalStateT (traverse nxt [1..n]) 0
 where nxt _ = do s <- get
                  r <- bar s
                  put $ s + 1
                  return r

bar :: Monad m
    => Int
    -> m [Int]
bar n = unfoldM step n
  where step k = return $ if k > 0 then Just (k, k - 1)
                                   else Nothing

main :: IO ()
main = do xs <- foo 3
          print xs

工作正常,生成此输出:

Davids-Air-2:haskell-rl dbanas$ stack runghc tst2.hs 
[[],[1],[2,1]]

但是,如果我稍微更改一下代码:

-- tst3.hs - showing breakage of monadic linkage.

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE FlexibleContexts #-}

import Protolude
import Control.Monad.Extra  (unfoldM)

data Dummy m = Dummy (Int -> m Int)

foo :: Monad m
    => Int
    -> Dummy m
    -> m [[Int]]
foo n d = evalStateT (traverse nxt [1..n]) 0
 where nxt _ = do s <- get
                  r <- bar s d
                  put $ s + 1
                  return r

bar :: Monad m
    => Int
    -> Dummy m
    -> m [Int]
bar n d = unfoldM step n
  where step k = return $ if k > 0 then Just (k, k - 1)
                                   else Nothing

main :: IO ()
main = do xs <- foo 3 $ Dummy (\n -> return n)
          print xs

我收到以下编译器错误:

Davids-Air-2:haskell-rl dbanas$ stack runghc tst3.hs 

tst3.hs:15:23: error:
    • Couldn't match type ‘m’ with ‘StateT Integer m’
      ‘m’ is a rigid type variable bound by
        the type signature for:
          foo :: forall (m :: * -> *). Monad m => Int -> Dummy m -> m [[Int]]
        at tst3.hs:(11,1)-(14,16)
      Expected type: StateT Integer m [[Int]]
        Actual type: m [[Int]]
    • In the first argument of ‘evalStateT’, namely
        ‘(traverse nxt [1 .. n])’
      In the expression: evalStateT (traverse nxt [1 .. n]) 0
      In an equation for ‘foo’:
          foo n d
            = evalStateT (traverse nxt [1 .. n]) 0
            where
                nxt _
                  = do s <- get
                       ....
    • Relevant bindings include
        nxt :: forall p. p -> m [Int] (bound at tst3.hs:16:8)
        d :: Dummy m (bound at tst3.hs:15:7)
        foo :: Int -> Dummy m -> m [[Int]] (bound at tst3.hs:15:1)
   |
15 | foo n d = evalStateT (traverse nxt [1..n]) 0
   |                       ^^^^^^^^^^^^^^^^^^^

结果证明解决方案非常简单,虽然我花了几个小时来推导:

          r <- lift $ bar s d

Davids-Air-2:haskell-rl dbanas$ stack runghc tst3.hs 
[[],[1],[2,1]]

我对此有一个理论并希望确认:

bar中运行的Monad与在foo中运行的Monad不同。 具体来说,在 foo 中运行的 Monad 是在 bar 中运行的 Monad 的 lifted 版本。 (准确地说,提升到 StateT 上下文中。)

tst2.hs 的情况下,两个 m 之间没有类型链接(foobar),这非常有效。 但是,在 tst3.hs 中,我提供了两者之间的类型链接,迫使它们成为同一个 Monad。 这就是编译器抱怨的原因。

我的解决方案有效,只是因为在 foo 中运行的 Monad 确实是在 bar 中运行的 lifted 版本。 如果这两个 Monad 完全不相关,那么我的解决方案 不会 有效。

都是这样吗?

当您从 main 调用 foo 时,您是说 Monad m 应该是 IO。因此,您从 Dummy (\n -> return n) 得到一个 Dummy IO(顺便说一下,这可能只是 Dummy return)。然后在 foo 中,您使用 Dummy IO 参数调用 bar,因此在 barMonad m 中将 m 设置为 IO ].但是,对 bar 的调用是在 StateT Int IO 内而不是 IO 内,因此出现错误。

如您所见,您可以使用 Dummy IO 调用 bar,然后将结果提升到 StateT Int IO。您也正确地观察到在某些情况下这是行不通的。

您可能需要考虑另一种解决方案。如果你实际上并不关心你的 Dummy 类型需要什么 monad(似乎是这种情况),你可以强制它在所有 monads 中工作:

newtype Dummy = Dummy (Int -> (forall m. Monad m => m Int))

这需要 RankNTypes 扩展。