Haskell: 将函数作为参数传递时出现刚性类型变量错误

Haskell: Rigid type variable error when passing function as argument

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

编译器希望我的类型签名更具体,但我不能,因为我需要使用具有不同类型参数的参数函数。就像在我的示例中一样,我将 Action IO ()Action IO String.

传递给它

如果我用 (Action IO a -> IO a) -> String -> IO () 代替 (Action IO () -> IO ()) -> String -> IO (),就像编译器要求的那样,带有 readFile 的调用会出错,因为它输出 IO String.

为什么会这样,我应该怎么做才能将此函数作为参数传递?

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

这是一个量词问题。类型

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的角度来看,这种类型变量绑定到一个固定的未知类型,由其他人选择:这就是"rigid" 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(即actionFile)的来电者必须选择a
    • g(即 actionFile)的调用者必须提供 Action IO a
    • 最后,g 必须提供 IO a
  • 来电者必须提供 String
  • 最后,actionFile 必须回答IO ()

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