簡體   English   中英

Haskell IO:將IO字符串轉換為“其他類型”

[英]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 aread總是返回Read類型類的成員。 所以t :: (Read a, Ord a) => Tree a 具體類型取決於文件的內容。

return將其參數包裝在monad中,因此return t的類型為Ord a, Read a => IO (Tree a) 由於return tdo塊中的最后一個語句,因此它成為fileRead的返回類型,所以

fileRead :: (Read a, Ord a) => IO (Tree a)

所以fileRead是一個包含在IOTree ,你不能直接將它傳遞給ins因為它需要一個Tree自己。 您無法將TreeIO取出,但您可以將函數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版本。 此代碼僅使用IOliftM的事實,因此使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM