[英]Haskell IO: convert IO String to “Other type”
我有一個Haskell程序,它將文件作為輸入並將其轉換為二叉搜索樹。
import System.IO
data Tree a = EmptyBST | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
ins :: Ord a => a -> (Tree a) -> (Tree a)
ins a EmptyBST = Node a EmptyBST EmptyBST
ins a (Node p left right)
| a < p = Node p (ins a left) right
| a > p = Node p left (ins a right)
| otherwise = Node p left right
lstToTree :: Ord a => [a] -> (Tree a)
lstToTree = foldr ins EmptyBST
fileRead = do file <- readFile "tree.txt"
let a = lstToTree (conv (words file))
return a
conv :: [String] -> [Int]
conv = map read
但是,當我運行以下命令時:
ins 5 fileRead
我收到以下錯誤:
<interactive>:2:7:
Couldn't match expected type `Tree a0'
with actual type `IO (Tree Int)'
In the second argument of `ins', namely `fileRead'
In the expression: ins 5 fileRead
In an equation for `it': it = ins 5 fileRead
請有人幫我嗎?
謝謝
如果您提供帶有類型簽名的fileRead
,您將能夠立即看到問題。 讓我們弄清楚GHC將在內部分配給fileRead
的類型注釋:
fileRead = do file <- readFile "tree.txt"
let t = lstToTree $ map read $ words file
return t
lstToTree :: Ord a => [a] -> Tree a
, read
總是返回Read
類型類的成員。 所以t :: (Read a, Ord a) => Tree a
。 具體類型取決於文件的內容。
return
將其參數包裝在monad中,因此return t
的類型為Ord a, Read a => IO (Tree a)
。 由於return t
是do
塊中的最后一個語句,因此它成為fileRead
的返回類型,所以
fileRead :: (Read a, Ord a) => IO (Tree a)
所以fileRead
是一個包含在IO
的Tree
,你不能直接將它傳遞給ins
因為它需要一個Tree
自己。 您無法將Tree
從IO
取出,但您可以將函數ins
“提升”到IO
monad中。
Control.Monad導出liftM :: Monad m => (a -> r) -> (ma -> mr)
。 它接受一個常規函數,並將其轉換為一個像IO
一樣的monad。 它實際上是fmap
的同義詞(在標准Prelude中),因為所有monad都是fmap
函數。 所以這個代碼,大致相當於@ us202的,需要的結果fileRead
,插入5
,並給你回包裹在一個結果IO
。
liftM (ins 5) fileRead
-- or --
fmap (ins 5) fileRead
我推薦fmap
版本。 此代碼僅使用IO
是liftM
的事實,因此使用liftM
意味着讀者可能還需要它作為monad。
“提升”是在包含在monad或functor中的值上使用純函數的一般技術。 如果你不熟悉解除(或者如果你對monad和functor感到困惑),我衷心地推薦Learn You A Haskell的第11-13章。
PS。 請注意, fileRead
的最后兩行可能應該合並,因為return
實際上沒有做任何事情:
fileRead :: (Read a, Ord a) => IO (Tree a)
fileRead = do file <- readFile "tree.txt"
return $ lstToTree $ map read $ words file
或者,因為它是一個足夠短的功能,你可以破除do
完全符號和使用fmap
再次:
fileRead :: (Read a, Ord a) => IO (Tree a)
fileRead = fmap (lstToTree . map read . words) (readFile "tree.txt")
編輯以回應您的評論:
Haskell是故意設計讓執行IO與普通的代碼代碼分離。 這有一個非常好的哲學原因:大多數Haskell函數都是“純粹的” - 也就是說,它們的輸出僅取決於輸入,就像數學中的函數一樣。 你可以運行一百萬次的純函數,你總能得到相同的結果。 我們喜歡純函數,因為它們不會意外地破壞程序的其他部分,它們允許懶惰,並且它們允許編譯器為您積極地優化代碼。
當然,在現實世界中我們需要一點點雜質。 像getLine
這樣的IO代碼不可能是純粹的(並且不執行IO的程序是無用的!)。 getLine
的結果取決於用戶鍵入的內容:您可以運行getLine
一百萬次並且每次都獲得不同的字符串。 Haskell利用類型系統來標記IO
類型的不純代碼。
問題的關鍵在於:如果對不純粹獲得的數據使用純函數,那么結果仍然不純,因為結果取決於用戶的行為 。 所以整個計算都屬於IO
monad。 當你希望把一個純粹的功能分為IO
必須抬起它,明確地(使用fmap
)或隱式(帶do
記號)。
這是Haskell中一個非常常見的模式 - 看看我上面的fileRead
版本。 我使用fmap
來操作具有純函數的不純IO
數據。
你不能真正逃脫IO monad(除了通過不安全的函數),但在你的情況下沒有實際需要這樣做:
main = do f <- fileRead
let newtree = ins 5 f
putStr $ show newtree
(現場演示: 這里 )
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.