Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell:将函数作为参数传递时发生刚性类型变量错误_Haskell_Types_Hindley Milner - Fatal编程技术网

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

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

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字符串

如果我将
(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报告中,因为它们已经变得如此流行。不要害怕使用它们,每个人都已经在这样做了。