2017-07-25 46 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

相關問題