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
。