2

GHC 说我的函数太笼统而不能作为参数传递。

这是重现错误的简化版本:

data Action m a = SomeAction (m a)


runAction :: Action m a -> m a
runAction (SomeAction ma) =  ma

-- Errors in here
actionFile :: (Action IO a -> IO a) -> String -> IO ()
actionFile actionFunc fileName = do
    actionFunc $ SomeAction $ readFile fileName
    actionFunc $ SomeAction $ putStrLn fileName


main :: IO ()
main =
    actionFile runAction "Some Name.txt"

这就是错误所说的:

 • Couldn't match type ‘a’ with ‘()’
      ‘a’ is a rigid type variable bound by
        the type signature for:
          actionFile :: forall a. (Action IO a -> IO a) -> String -> IO ()
        at src/Lib.hs:11:15
      Expected type: Action IO a
        Actual type: Action IO ()

编译器希望我的类型签名更具体,但我不能,因为我需要将参数函数与不同类型的参数一起使用。就像在我的示例中一样,我将它传递给 anAction IO ()和 an Action IO String

如果我像编译器询问的那样用错误替换调用,(Action IO a -> IO a) -> String -> IO ()因为它输出一个.(Action IO () -> IO ()) -> String -> IO ()readFileIO String

为什么会发生这种情况,我应该怎么做才能将此函数作为参数传递?

我知道如果我只是runAction在我的actionFile函数中使用一切都会正常工作,但在我的真实代码runAction中是一个部分应用的函数,它是从 IO 计算的结果构建的,所以它在编译时不可用。

4

1 回答 1

6

这是一个量词问题。类型

actionFile :: (Action IO a -> IO a) -> String -> IO ()

意味着,如 GHC 错误所报告的,

actionFile :: forall a. (Action IO a -> IO a) -> String -> IO ()

其中规定了以下内容:

  • 调用者必须选择一种类型a
  • 调用者必须提供一个函数g :: Action IO a -> IO a
  • 调用者必须提供一个String
  • 最后,actionFile必须回答IO ()

请注意,这a是由调用者选择的,而不是由actionFile. 从 的角度来看actionFile,这种类型变量绑定到一个固定的未知类型,由其他人选择:这是 GHC 在错误中提到的“刚性”类型变量。

但是,actionFile调用g传递Action IO ()参数(因为putStrLn)。这意味着actionFile要选择a = ()。由于调用者可以选择不同a的 ,因此会引发类型错误。

此外,actionFile还想调用g传递一个Action IO String参数(因为readFile),所以我们也想选择a = String。这意味着g必须接受a我们想要的任何选择。

正如 Alexis King 所提到的,一个解决方案可能是移动量词并使用 rank-2 类型:

actionFile :: (forall a. Action IO a -> IO a) -> String -> IO ()

这种新类型意味着:

  • 调用者必须提供一个函数g :: forall a. Action IO a -> IO a
    • g(ie, )的调用者actionFile必须选择a
    • g(ie, )的调用者actionFile必须提供Action IO a
    • 最后,g必须提供一个IO a
  • 调用者必须提供一个String
  • 最后,actionFile必须回答IO ()

这使得可以根据actionFile需要进行选择a

于 2017-07-25T07:52:29.957 回答