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

时间:2017-07-25 07:20:02

标签: 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 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计算的结果构建的,因此在编译时无法使用。

1 个答案:

答案 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