如果没有 System.IO.Unsafe,我如何获得 Monad 的值?
How can I get the value of a Monad without System.IO.Unsafe?
我刚刚开始学习 Haskell,今天我的第一个项目开始运行。它是一个小程序,使用 Network.HTTP.Conduit
和 Graphics.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
。
我刚刚开始学习 Haskell,今天我的第一个项目开始运行。它是一个小程序,使用 Network.HTTP.Conduit
和 Graphics.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
。