如何在没有System.IO.Unsafe的情况下获得Monad的价值?

时间:2015-06-01 22:42:35

标签: haskell web-crawler monads

我刚刚开始学习Haskell并让我的第一个项目在今天工作。它是一个小程序,使用Network.HTTP.ConduitGraphics.Rendering.Charthaskell-chart)来绘制特定问题的Google搜索结果数量,其中包含不断变化的数字。

我的问题是来自管道包的simple-http返回一个monad(我希望我理解monads的概念正确...),但我只想在其中使用ByteString,其中包含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

1 个答案:

答案 0 :(得分:16)

这是谎言

getResultCounter :: String -> Integer

上面的类型签名有希望得到的整数仅取决于输入字符串,但事实并非如此:Google可以通过一次调用添加/删除结果,从而影响输出。

让类型更诚实,我们得到

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