2017-07-25 80 views
2

GHC说我的函数太泛泛,无法作为参数传递。Haskell:将函数作为参数传递时的刚性类型变量错误

这里是一个简化版本,再现错误:

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计算的结果内置了部分应用功能,所以它不是在编译时间。

+5

你想要一个等级2的类型,但标准的Haskell只允许等级1的类型。启用'RankNTypes'扩展并将'actionFile'的类型更改为'(操作IO a - > IO a) - > String - > 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传递(因为readFile)的Action IO String说法,所以我们也想选择a = String。这意味着g必须接受我们希望的任何a的选择。

正如亚历克西斯国王提到,一个解决方案是移动量词和使用等级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如通缉。

+0

谢谢你。你知道任何其他解决方案不会使用语言扩展吗? –

+2

@MarceloLazaroni不是真正的。你可以制作actionFile:(Action IO() - > IO()) - >(Action IO String - > IO String) - > String - > IO()'但它会很奇怪。另外请注意,现代库和应用程序利用许多扩展名是很常见的。我甚至会说没有人用普通的Haskell编写严肃的代码。大多数扩展都是无害的,可以说很多扩展都应该包含在修改后的Haskell报告中,仅仅是因为它们已经变得如此受欢迎。不要害怕使用它们,每个人都已经这么做了。 – chi

相关问题