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

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

我知道,如果仅在runAction函数中使用actionFile,一切都会正常工作,但是在我的真实代码中,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的角度来看,此类类型变量绑定到由其他人选择的固定未知类型:这是GHC在错误中提到的“刚性”类型变量。

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

此外,actionFile还希望通过g参数调用Action IO String(由于readFile),因此我们也想选择a = String。这意味着g必须接受我们希望的任何a选择。

如Alexis King所述,解决方案可能是移动量词并使用等级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

09-25 21:50