Haskell 如果没有System.IO.Unsafe,如何获取Monad的值?

Haskell 如果没有System.IO.Unsafe,如何获取Monad的值?,haskell,web-crawler,monads,Haskell,Web Crawler,Monads,我刚开始学习Haskell,今天开始了我的第一个项目。这是一个小程序,它使用Network.HTTP.conduct和Graphics.Rendering.Chart()绘制一个特定问题的谷歌搜索结果量,其中包含一个不断变化的数字 我的问题是,来自导管包的simple http返回一个monad(我希望我正确理解monad的概念…),但我只想在其中使用ByteString,它包含网站的html代码。因此,直到现在,我还是使用download=unsafePerformIO$simplehttpu

我刚开始学习Haskell,今天开始了我的第一个项目。这是一个小程序,它使用
Network.HTTP.conduct
Graphics.Rendering.Chart
()绘制一个特定问题的谷歌搜索结果量,其中包含一个不断变化的数字

我的问题是,来自导管包的
simple http
返回一个monad(我希望我正确理解monad的概念…),但我只想在其中使用ByteString,它包含网站的html代码。因此,直到现在,我还是使用
download=unsafePerformIO$simplehttpurl
来在以后使用它,而不关心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
import System.IO.Unsafe
导入Network.HTTP.conductor(simpleHttp)
将限定数据.ByteString.Lazy.Char8作为L导入
导入Graphics.Rendering.Chart.Easy
导入Graphics.Rendering.Chart.Backend.Cairo
numchars::[Char]
numchars=“1234567890”
isNum::Char->Bool
isNum=(\x->x`elem`numchars)
main=do
putStrLn“请输入您的搜索(第一个“X”将被替换):
通过testring搜索L
dropByteStringUntil str cont=helper str cont 0
其中,helper s bs n |(bs==L.empty)=L.empty
|(n>=长度s)=bs
|((s!!n)=L.头bs)=助手s(L.尾bs)(n+1)
|((s!!n)/=L.头bs)=助手s(L.尾bs)0
--获取ByteString,直到找到所需的字符串
takeByteStringUntil::String->L.ByteString->L.ByteString
takeByteStringUntil str cont=helper str cont 0
其中,helper s bs n | bs==L.empty=bs
|n>=长度s=L.空
|s!!n==L.head bs=L.head bs`L.cons`
助手s(左尾bs)(n+1)
|s!!n/=L.head bs=L.head bs`L.cons`
助手s(左尾bs)0
--将字符串中的第一个“X”替换为给定值的显示值
replaceX::(显示a)=>String->a->String
replaceX str x | str==“”“
|头str=='X'=显示X++尾str
|否则=头部str:replaceX(尾部str)x
这是一个谎言:

上面的类型签名很有希望得到的整数只取决于输入字符串,但事实并非如此: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)
但我们能做到

res <- mapM getResultCounter searches

res顺便说一句,
Data.Char
提供了
isDigit
,这比用这种方式定义自己的函数更方便有效。啊,谢谢,我不知道这个函数存在。@GuntherRocket:即使它不存在,你也可以将它内联:
getResultCounter search=read$filter(`elem`“0123456789”)$L.unpack parse
。不需要在顶层定义它!谢谢现在可以了。我已经尝试过类似的东西,但是我错过了mapM,我想我必须更改所有使用ByteString的函数。不管怎样,现在我对单子有了更多的了解。
let res = map getResultCounter searches
res <- mapM getResultCounter searches