Haskell: 将函数作为参数传递时出现刚性类型变量错误
Haskell: Rigid type variable error when passing function as argument
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
.
为什么会这样,我应该怎么做才能将此函数作为参数传递?
我知道如果我只是在我的 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
- 来电者必须提供
String
- 最后,
actionFile
必须回答 IO ()
请注意 a
是由调用者选择的,而不是 actionFile
。从actionFile
的角度来看,这种类型变量绑定到一个固定的未知类型,由其他人选择:这就是"rigid" 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
。
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
.
为什么会这样,我应该怎么做才能将此函数作为参数传递?
我知道如果我只是在我的 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
- 来电者必须提供
String
- 最后,
actionFile
必须回答IO ()
请注意 a
是由调用者选择的,而不是 actionFile
。从actionFile
的角度来看,这种类型变量绑定到一个固定的未知类型,由其他人选择:这就是"rigid" 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
。