Haskell:将函数作为参数传递时发生刚性类型变量错误
GHC说我的函数太一般,不能作为参数传递 以下是复制错误的简化版本:Haskell:将函数作为参数传递时发生刚性类型变量错误,haskell,types,hindley-milner,Haskell,Types,Hindley Milner,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 fileNam
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字符串
如果我将(Action IO()->IO a->IO a)->String->IO()
替换为(Action IO()->IO())->String->IO()
,就像编译器要求的那样,调用会出现readFile
错误,因为它会输出IO字符串
为什么会发生这种情况?我应该怎么做才能将此函数作为参数传递?
我知道,如果我只在我的
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
- 调用者必须提供
字符串
- 最后,
必须使用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 ()
这种新类型意味着:
- 调用方必须为所有a提供一个函数
g::函数。操作IO a->IO a
(即g
)的调用者必须选择actionFile
a
(即g
)的调用者必须提供actionFile
操作IO a
- 最后,
必须提供一个g
IO a
- 调用者必须提供
字符串
- 最后,
必须使用actionFile
IO()
这使得
actionFile
可以根据需要选择a
。您需要秩2类型,但标准Haskell只允许秩1类型。启用RankNTypes
扩展名,并将actionFile
的类型更改为(对于所有a.Action IO a->IO a->String->IO()
)。它起作用了。我将阅读更多关于类型排序的内容,以了解正在发生的事情,以及我可能会放弃这种语言扩展的哪些保证。谢谢,谢谢你。你知道还有其他不使用语言扩展的解决方案吗?@MarceloLazaroni不是真正的解决方案。你可以制作actionFile:(Action-IO()->IO())->(Action-IO-String->IO-String)->String->IO()
,但这会很奇怪。另外,请注意,现代库和应用程序利用许多扩展是非常常见的。我甚至想说,没有人用普通的Haskell编写严肃的代码。大多数扩展都是无害的,可以说其中的大部分应该包括在修改后的Haskell报告中,因为它们已经变得如此流行。不要害怕使用它们,每个人都已经在这样做了。