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计算的结果构建的,因此在编译时无法使用。
答案 0 :(得分: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
的来电者(即actionFile
)必须选择a
g
的来电者(即actionFile
)必须提供Action IO a
g
必须提供IO a
String
actionFile
必须以IO ()
这使actionFile
可以根据需要选择a
。