[英]How can I parse the IO String in Haskell?
我遇到了Haskell的問題。 我的文本文件看起來像這樣:
5.
7.
[(1,2,3),(4,5,6),(7,8,9),(10,11,12)].
我不知道如何獲得前兩個數字(上面的2和7)和最后一行的列表。 每行末尾有點。
我試圖構建一個解析器,但是名為'readFile'的函數返回Monad,稱為IO String。 我不知道如何從這種類型的字符串中獲取信息。
我更喜歡在一系列字符上工作。 也許有一個函數可以從'IO String'轉換為[Char]?
我認為你對Haskell中的IO有一個基本的誤解。 特別是,你這樣說:
也許有一個函數可以從'IO String'轉換為[Char]?
不,沒有1 ,並且沒有這樣的功能是Haskell最重要的事情之一。
Haskell是一種非常有原則的語言。 它試圖保持“純”函數(沒有任何副作用,並在給出相同的輸入時始終返回相同的結果)和“不純”函數(具有類似讀取文件,打印等副作用)之間的區別到屏幕,寫入磁盤等)。 規則是:
代碼標記為純或不純的方式是使用類型系統。 當你看到像這樣的函數簽名時
digitToInt :: String -> Int
你知道這個功能是純粹的。 如果你給它一個String
,它將返回一個Int
,而且如果你給它相同的String
,它將總是返回相同的Int
。 另一方面,功能簽名就像
getLine :: IO String
是不純的 ,因為String
的返回類型標有IO
。 顯然getLine
(讀取一行用戶輸入)並不總是返回相同的String
,因為它取決於用戶鍵入的內容。您不能在純代碼中使用此函數,因為添加即使是最小的雜質也會污染純粹的代碼。 一旦你去IO
你永遠不會回去。
您可以將IO
視為包裝器。 當你看到一個特定的類型,例如x :: IO String
,你應該將其解釋為“ x
是一個動作,當執行時,執行一些任意的I / O然后返回String
類型的東西”(注意在Haskell, String
和[Char]
是完全相同的東西)。
那么如何訪問IO
操作中的值? 幸運的是,函數main
的類型是IO ()
(它是一個執行一些I / O和返回()
,它與不返回任何內容相同。 因此,您始終可以在main
使用IO
功能。 當您執行Haskell程序時,您正在執行的是運行main
函數,這會導致程序定義中的所有I / O實際執行 - 例如,您可以從文件讀取和寫入,請求用戶輸入,寫給stdout等等
您可以考慮構造一個像這樣的Haskell程序:
IO
標記(基本上,你把它放在一個do
塊中) do
塊中 - 這些是“純”函數。 main
功能將您按照順序定義的I / O操作排列在一起,使程序按您希望的方式執行(在任何您喜歡的位置穿插純函數)。 main
,將導致執行所有這些I / O操作。 那么,考慮到這一切,你如何編寫你的程序? 好吧,功能
readFile :: FilePath -> IO String
將文件作為String
讀取。 所以我們可以使用它來獲取文件的內容。 功能
lines:: String -> [String]
在換行符上拆分一個String
,所以現在你有一個String
列表,每個String
對應一個文件行。 功能
init :: [a] -> [a]
滴從列表中(這將擺脫最終的最后一個元素.
每行)。 功能
read :: (Read a) => String -> a
獲取一個String
並將其轉換為任意Haskell數據類型,例如Int
或Bool
。 合理地結合這些功能將為您提供程序。
請注意,實際需要執行任何I / O的唯一時間是您正在讀取文件。 因此,這是程序中唯一需要使用IO
標記的部分。 程序的其余部分可以“純粹”編寫。
聽起來你需要的是文章The IO Monad For Mon Is Not Care ,這應該可以解釋你的很多問題。 不要被“monad”這個術語嚇到 - 你不需要理解monad是什么來編寫Haskell程序(注意這個段落是我答案中唯一一個使用“monad”這個詞,盡管我承認我現在已經使用了四次......)
這是我想寫的程序(我想)
run :: IO (Int, Int, [(Int,Int,Int)])
run = do
contents <- readFile "text.txt" -- use '<-' here so that 'contents' is a String
let [a,b,c] = lines contents -- split on newlines
let firstLine = read (init a) -- 'init' drops the trailing period
let secondLine = read (init b)
let thirdLine = read (init c) -- this reads a list of Int-tuples
return (firstLine, secondLine, thirdLine)
要回答關於將lines
應用於readFile text.txt
輸出的npfedwards
注釋,您需要意識到readFile text.txt
為您提供了一個IO String
,並且只有當您將它綁定到一個變量(使用contents <-
)時訪問底層String
,以便您可以對其應用lines
。
記住:一旦你進入IO
,你永遠不會回去。
1我故意忽略unsafePerformIO
因為正如名稱所暗示的那樣,它非常不安全! 除非你真的知道自己在做什么,否則不要使用它。
作為編程菜鳥,我也被IO
迷惑了。 請記住,如果你去IO
你永遠不會出來。 克里斯寫了一個很好的解釋原因 。 我只是想提供一些關於如何在monad中使用IO String
示例。 我將使用getLine讀取用戶輸入並返回IO String
。
line <- getLine
所有這一切都將getLine
的用戶輸入綁定到名為line
的值。 如果你在ghci中輸入這個,並輸入:type line
它將返回:
:type line
line :: String
可是等等! getLine
返回一個IO String
:type getLine
getLine :: IO String
所以,發生了什么事IO
從尼斯getLine
? <-
發生了什么事。 <-
是你的IO
朋友。 它允許您顯示monad中IO
所污染的值,並將其與正常功能一起使用。 Monads易於識別,因為它們以do
開頭。 像這樣:
main = do
putStrLn "How much do you love Haskell?"
amount <- getLine
putStrln ("You love Haskell this much: " ++ amount)
如果你像我一樣,你很快就會發現, liftIO
是你的下一個最好的單子的朋友,那$
幫助減少你需要寫括號的數量。
那么如何從readFile
獲取信息? 好吧,如果readFile
的輸出是IO String
如下所示:
:type readFile
readFile :: FilePath -> IO String
那么你所需要的就是你的友好<-
:
yourdata <- readFile "samplefile.txt"
現在,如果類型,在ghci中,檢查的類型yourdata
你會發現這是一個簡單的String
。
:type yourdata
text :: String
正如人們已經說過的,如果你有兩個函數,一個是readStringFromFile :: FilePath -> IO String
,另一個是doTheRightThingWithString :: String -> Something
,那么你真的不需要從IO
轉義一個字符串,因為你可以以各種方式結合這兩個功能:
使用fmap
for IO
( IO
是Functor
):
fmap doTheRightThingWithString readStringFromFile
使用(<$>)
表示IO
( IO
是Applicative
和(<$>) == fmap
):
import Control.Applicative
...
doTheRightThingWithString <$> readStringFromFile
使用liftM
for IO
( liftM == fmap
):
import Control.Monad
...
liftM doTheRightThingWithString readStringFromFile
使用(>>=)
表示IO
( IO
為Monad
, fmap == (<$>) == liftM == \\fm -> m >>= return . f
):
readStringFromFile >>= \string -> return (doTheRightThingWithString string)
readStringFromFile >>= \string -> return $ doTheRightThingWithString string
readStringFromFile >>= return . doTheRightThingWithString
return . doTheRightThingWithString =<< readStringFromFile
隨着do
記號:
do
...
string <- readStringFromFile
-- ^ you escape String from IO but only inside this do-block
let result = doTheRightThingWithString string
...
return result
每次你都會得到IO Something
。
你為什么要這樣做呢? 好吧,有了這個,您將擁有您所用語言的純粹和引用透明的程序(函數)。 這意味着每個類型都是無IO的函數是純粹的和引用透明的 ,因此對於相同的參數,它將返回相同的值。 例如, doTheRightThingWithString
將為同一個String
返回相同的Something
。 但是,不是無IO的readStringFromFile
每次都可以返回不同的字符串(因為文件可以更改),因此您無法從IO
轉義這樣的不正確值。
如果您有這種類型的解析器:
myParser :: String -> Foo
然后你用它來閱讀文件
readFile "thisfile.txt"
然后你可以使用讀取和解析文件
fmap myParser (readFile "thisfile.txt")
結果將具有類型IO Foo
。
fmap
意味着myParser
在IO內部運行。
想到它的另一種方法是,而myParser :: String -> Foo
, fmap myParser :: IO String -> IO Foo
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.