代码之家  ›  专栏  ›  技术社区  ›  dbanas

当我引入一个使用单子作为参数的新参数时,为什么函数之间的一元链接会中断?

  •  1
  • dbanas  · 技术社区  · 7 年前

    以下代码:

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

    我对此有一个理论,希望得到证实:

    Monad在中运行 bar 与在中运行的Monad不同 foo 。 具体来说,Monad在 foo公司 已解除 在中运行的Monad版本 酒吧 (提升到a StateT 确切地说,是上下文。)

    假使 tst2.hs 如果两者之间没有类型链接 m s(共个) foo公司 酒吧 ),此操作非常有效。 然而,在 tst3.hs 我提供了两者之间的类型链接,迫使它们成为同一个Monad。 这就是编译器抱怨的原因。

    我的解决方案有效,只是因为Monad在 foo公司 真的是 已解除 在中运行的版本 酒吧 。 如果这两个单子完全不相关,那么我的解决方案 工作

    一切都对吗?

    1 回复  |  直到 7 年前
        1
  •  4
  •   user2297560    7 年前

    当你打电话的时候 foo 从…起 main ,你是这么说的 Monad m 应该是 IO 因此,您可以 Dummy IO 从…起 Dummy (\n -> return n) (这可能只是 Dummy return ,顺便提一下)。然后在里面 foo公司 ,您正在呼叫 bar 使用 虚拟IO 参数,因此设置 m IO 在里面 酒吧 单子m .然而 酒吧 在里面 StateT Int IO 而不是 IO ,因此出现错误。

    正如您所发现的,您可以使用 虚拟IO 然后将结果提升到 StateT Int IO 。您也可以正确地观察到,在某些情况下,这可能不起作用。

    您可能需要考虑另一种解决方案。如果你真的不在乎 Dummy 类型需要(似乎是这样),您可以强制它在所有monad中都工作:

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

    这需要 RankNTypes 扩大