如果没有 System.IO.Unsafe,我如何获得 Monad 的值?

How can I get the value of a Monad without System.IO.Unsafe?

我刚刚开始学习 Haskell,今天我的第一个项目开始运行。它是一个小程序,使用 Network.HTTP.ConduitGraphics.Rendering.Chart (haskell-chart) 绘制特定问题的 google 搜索结果的数量,其中的数字不断变化。

我的问题是管道包 returns 中的 simple-http 一个 monad(我希望我理解 monad 的概念是对的...),但我只想使用 ByteString inside它包含网站的 html 代码。所以直到现在我使用 download = unsafePerformIO $ simpleHttp url 稍后使用它而不关心 monad - 我想这不是最好的方法。

所以:有没有更好的解决方案,这样我就不必在整个评估过程中随身携带 monad?还是保留返回结果的方式(使用 monad)会更好?

这是完整的程序 - 提到的行在 getResultCounter 中。如果事情编码不太好并且可以做得更好,也请注意:

import System.IO.Unsafe
import Network.HTTP.Conduit (simpleHttp) 
import qualified Data.ByteString.Lazy.Char8 as L
import Graphics.Rendering.Chart.Easy
import Graphics.Rendering.Chart.Backend.Cairo

numchars :: [Char]
numchars = "1234567890"

isNum :: Char -> Bool
isNum = (\x -> x `elem` numchars) 

main = do
    putStrLn "Please input your Search (The first 'X' is going to be replaced): "
    search <- getLine
    putStrLn "X ranges from: "
    from <- getLine
    putStrLn "To: "
    to <- getLine
    putStrLn "In steps of (Only whole numbers are accepted):"
    step <- getLine
    putStrLn "Please have some patience..."
    let range = [read from,(read from + read step)..read to] :: [Int]
    let searches = map (replaceX search) range
    let res = map getResultCounter searches
    plotList search ([(zip range res)] :: [[(Int,Integer)]])
    putStrLn "Done."

-- Creates a plot from the given data
plotList name dat = toFile def (name++".png") $ do
    layout_title .= name
    plot (line "Results" dat)

-- Calls the Google-site and returns the number of results
getResultCounter :: String -> Integer
getResultCounter search = read $ filter isNum $ L.unpack parse :: Integer
    where url = "http://www.google.de/search?q=" ++ search
              download = unsafePerformIO $ simpleHttp url -- Not good 
              parse = takeByteStringUntil "<" 
                      $ dropByteStringUntil "id=\"resultStats\">" download

-- Drops a ByteString until the desired String is found
dropByteStringUntil :: String -> L.ByteString -> L.ByteString
dropByteStringUntil str cont = helper str cont 0
    where helper s bs n | (bs == L.empty) = L.empty
                        | (n >= length s) = bs
                        | ((s !! n) == L.head bs) = helper s (L.tail bs) (n+1)
                        | ((s !! n) /= L.head bs) = helper s (L.tail bs) 0

-- Takes a ByteString until the desired String is found
takeByteStringUntil :: String -> L.ByteString -> L.ByteString
takeByteStringUntil str cont = helper str cont 0
    where helper s bs n | bs == L.empty = bs
                        | n >= length s = L.empty
                        | s !! n == L.head bs = L.head bs `L.cons` 
                                                helper s (L.tail bs) (n + 1)
                        | s !! n /= L.head bs = L.head bs `L.cons` 
                                                helper s (L.tail bs) 0

-- Replaces the first 'X' in a string with the show value of the given value
replaceX :: (Show a) => String -> a -> String
replaceX str x | str == "" = ""
               | head str == 'X' = show x ++ tail str
               | otherwise = head str : replaceX (tail str) x

这是一个谎言:

getResultCounter :: String -> Integer

上面的类型签名承诺生成的整数仅取决于输入字符串,但事实并非如此:Google 可以 add/remove 从一个调用到另一个调用的结果,影响输出。

让类型更诚实,我们得到

getResultCounter :: String -> IO Integer

这老老实实承认要和外界互动。然后代码很容易适应:

getResultCounter search = do
    let url = "http://www.google.de/search?q=" ++ search
    download <- simpleHttp url    -- perform IO here
    let parse = takeByteStringUntil "<" 
                      $ dropByteStringUntil "id=\"resultStats\">" download
    return (read $ filter isNum $ L.unpack parse :: Integer)

以上,我尽量保留了代码的原始结构

现在,在main我们不能再做

let res = map getResultCounter searches

但我们可以做到

res <- mapM getResultCounter searches

导入后 Control.Monad