2013-04-03 132 views
4

正如我一直在學習haskell,我已經享受純粹的部分,但現在我蹣跚通過monadic和IO部分,並可能經歷了一些人真正感到憤怒的語言。我解決了一個項目euler問題,我簡單地想要一個可變數組,因爲我必須經常通過索引來更新元素。我試過Vectors,但無法讓它們工作,所以我嘗試了Data.Array.IO。我可以讀取和寫入元素,但無法以我想要的方式在終端中顯示數組。到目前爲止,我有這個。顯示IO陣列

test = do 
    arr <- newArray (1,10) 37 :: IO (IOArray Int Int) 
    a <- readArray arr 1 
    writeArray arr 1 64 
    b <- readArray arr 1 
    dispArray arr 
    return() 

dispArray arr = do 
    (a,b) <- getBounds arr 
    printf "[" 
    dispArray' arr a 
    printf "]\n" 
     where dispArray' arr i = do 
       (a,b) <- getBounds arr 
       if i < a || i > b 
        then return() 
        else do 
         v <- readArray arr i 
         print v 
         dispArray' arr (i+1) 

這正如你所期望的輸出中是這樣的:

[64 
37 
37 
37 
37 
37 
37 
37 
37 
37 
] 

但是,這是不方便的,我想這個[64,37,37,37....這樣。我見過類似toList的功能,但我不想要這樣的功能。我不想在每次顯示時轉換爲列表。所以我想我需要使用printf。所以我用printf " %s," (show v)替換了print v。但是這不能編譯。我不知道爲什麼。我認爲這將是因爲print :: Show a => a -> IO()show :: Show a => a -> String所以爲什麼它不會工作,因爲%s表示一個字符串?所以我接着打電話給對方。看看printf是否可以工作。

printf " %s," "hello" 
print v 

來編譯和顯示:

[ hello,64 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
hello,37 
] 

我爲什麼不能用show v?爲什麼Haskell IO對初學者如此憤怒?

+0

只是出於好奇,究竟是想用'的printf「%S」(編譯時收到錯誤消息show v)'? – jwodder

+0

'無法推斷(PrintfType(m a0)) 由'dispArray'的歧義檢查產生 我試過通過'::'添加類型,但是我覺得monads的類型是令人難以置信的神祕。 – DiegoNolan

+0

Ps這是'ST'陣列的一個很好的用例。他們讓你有一個可變的東西隱藏在一個純粹的功能接口 – jozefg

回答

6

這是一個有趣的類型檢查謎題。

該呼叫到printf產生的錯誤信息是

Could not deduce (PrintfType (m a0)) 
    arising from the ambiguity check for `dispArray' 

短語Could not deduceambiguity通常在一個事實,即具有GHC以結束這個程序應該如何被鍵入 不足型信息提示。這可能是一個真正的類型錯誤,但也可以通過提供更多類型信息來解決它(這裏就是這種情況)。

這裏的罪魁禍首真的是printf,加上可變數組接口的靈活性,而不是Haskell的IO系統。 printf的類型是一個巧妙的黑客攻擊,但仍然是黑客攻擊。爲了知道那僅僅依靠格式字符串各類參數的靈活數量,printf有一個類型,是不是很安全,也非常豐富:

printf :: PrintfType r => String -> r 

所以我們真正知道肯定的是,第一個參數是類型String。其餘的可以是類型爲PrintfType的任何類型r

實例的詳細信息無關緊要。有趣的是,show產生String,如果我們運用printf的格式字符串,然後一個show -produced第二個字符串,我們仍然留下了一個相當不提供信息的類型:

> :t printf "%s," (show 2) 
printf "%s," (show 2) :: PrintfType t => t 

特別是,沒有跡象顯示這裏的結果是在IO單子中。

這通常不會是一個問題,如果GHC可以從上下文中得出結論,你在IO。但是在dispArray'中,您正在調用的唯一其他功能是readArraygetBoundsreturn(和dispArray'遞歸)。這些功能都沒有說明它也住在IO之內。特別是,所有的陣列功能超載過單子,例如:

getBounds :: (Ix i, MArray a e m) => a i e -> m (i, i) 

(事實上,getBounds可能,例如,同樣工作在ST單子上下文。),這樣就根本沒有在dispArray'那確定你住在IO。而這又意味着GHC無法解決printf的類型。

正如我所說,這是printf所需靈活性的結果,printf本身無法提供此信息,並且必須在外部可用。

該解決方案非常簡單。作爲一個評論認爲,這足以將調用的結果類型註釋到printf

printf "%s," (show v) :: IO() 

當你使用printf無論如何(如果你實際上只在十進制數字陣列感興趣),你也可以使用:

printf "%d," v :: IO() 

這也將足夠了(但不太清楚的讀者),給出的dispArray'定義內的任何其他類型的簽名,使其固定收益類型爲IO()。例如,你可以註釋return()then分枝的if表達:

return() :: IO() 
6

你想要的咒語是:

putStr (show v) 

打印出v沒有一個換行符。

+0

'putStr $ show v'但爲什麼不能使用printf,如果我想在較少的行中使用不錯的格式? – DiegoNolan

+3

很難說,除非你做你的榜樣容易,我編的,但我的猜測是,你正在由單態限制咬傷,因爲你沒有提供明確的類型簽名。你的錯誤與Haskell'IO'無關。 GHC根本無法推斷出'printf'應該是什麼類型,因爲它濫用了類型魔法。 –