簡體   English   中英

如何在Haskell中解析IO String?

[英]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是一種非常有原則的語言。 它試圖保持“純”函數(沒有任何副作用,並在給出相同的輸入時始終返回相同的結果)和“不純”函數(具有類似讀取文件,打印等副作用)之間的區別到屏幕,寫入磁盤等)。 規則是:

  1. 您可以在任何地方使用純函數(在其他純函數中,或在不純函數中)
  2. 您只能在其他不純函數中使用不純函數。

代碼標記為純或不純的方式是使用類型系統。 當你看到像這樣的函數簽名時

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程序:

  • 所有執行I / O的代碼都獲得了IO標記(基本上,你把它放在一個do塊中)
  • 不需要執行I / O的代碼不需要在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數據類型,例如IntBool 合理地結合這些功能將為您提供程序。

請注意,實際需要執行任何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 IOIOFunctor ):

fmap doTheRightThingWithString readStringFromFile

使用(<$>)表示IOIOApplicative(<$>) == fmap ):

import Control.Applicative

...

doTheRightThingWithString <$> readStringFromFile

使用liftM for IOliftM == fmap ):

import Control.Monad

...

liftM doTheRightThingWithString readStringFromFile

使用(>>=)表示IOIOMonadfmap == (<$>) == 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 -> Foofmap myParser :: IO String -> IO Foo

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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