osh*_*hko 19 oop io state haskell
经过多年的OOP,我正在学习Haskell.
我正在写一个功能和状态很少的愚蠢的网络蜘蛛.
我不知道如何在FP世界中做到这一点.
在OOP世界中,这个蜘蛛可以像这样设计(按用法):
Browser b = new Browser()
b.goto(“http://www.google.com/”)
String firstLink = b.getLinks()[0]
b.goto(firstLink)
print(b.getHtml())
Run Code Online (Sandbox Code Playgroud)
此代码加载http://www.google.com/,然后"点击"第一个链接,加载第二页的内容,然后打印内容.
class Browser {
goto(url: String) : void // loads HTML from given URL, blocking
getUrl() : String // returns current URL
getHtml() : String // returns current HTML
getLinks(): [String] // parses current HTML and returns a list of available links (URLs)
private _currentUrl:String
private _currentHtml:String
}
Run Code Online (Sandbox Code Playgroud)
它可能同时拥有2个或"浏览器",具有自己独立的状态:
Browser b1 = new Browser()
Browser b2 = new Browser()
b1.goto(“http://www.google.com/”)
b2.goto(“http://www.stackoverflow.com/”)
print(b1.getHtml())
print(b2.getHtml())
Run Code Online (Sandbox Code Playgroud)
问题:展示如何从scracth(类似浏览器的API,可能有多个独立实例)在Haskell中设计这样的东西?请给出一个代码段.
注意:为简单起见,请跳过getLinks()函数的详细信息(其简单而无趣).
我们还假设有一个API函数
getUrlContents :: String -> IO String
Run Code Online (Sandbox Code Playgroud)
打开HTTP连接并返回给定URL的HTML.
更新:为什么要有州(或可能不是)?
API可以具有更多功能,而不仅仅是单个"加载和解析结果".
我没有添加它们以避免复杂性.
此外,它可以通过发送每个请求来关注HTTP Referer标头和cookie,以模拟真实的浏览器行为.
请考虑以下情形:
有这样的场景,我作为开发人员希望尽可能地将其转移到代码:
Browser b = new Browser()
b.goto("http://www.google.com/")
b.typeIntoInput(0, "haskell")
b.clickButton("Google Search") // b.goto(b.finButton("Google Search"))
b.clickLink("2") // b.goto(b.findLink("2"))
b.clickLink("3")
print(b.getHtml())
Run Code Online (Sandbox Code Playgroud)
此方案的目标是在一组操作之后获取最后一页的HTML.另一个不太明显的目标是保持代码紧凑.
如果Browser有一个状态,它可以发送HTTP Referer头和cookie,同时隐藏自身内部的所有机制并提供漂亮的API.
如果浏览器没有状态,开发人员可能会传递所有当前的URL/HTML/Cookies - 这会给场景代码增加噪音.
注意:我猜有外面的库可以在Haskell中删除HTML,但我的目的不是要废弃HTML,而是要了解如何在Haskell中正确设计这些"黑盒子"的东西.
Ale*_*nov 12
在描述问题时,根本不需要状态:
data Browser = Browser { getUrl :: String, getHtml :: String, getLinks :: [String]}
getLinksFromHtml :: String -> [String] -- use Text.HTML.TagSoup, it should be lazy
goto :: String -> IO Browser
goto url = do
-- assume getUrlContents is lazy, like hGetContents
html <- getUrlContents url
let links = getLinksFromHtml html
return (Browser url html links)
Run Code Online (Sandbox Code Playgroud)
它可能同时拥有2个或"浏览器",具有自己独立的状态:
你显然可以拥有你想要的任意数量,并且它们不会相互干扰.
现在相当于你的片段.第一:
htmlFromGooglesFirstLink = do
b <- goto "http://www.google.com"
let firstLink = head (links b)
b2 <- goto firstLink -- note that a new browser is returned
putStr (getHtml b2)
Run Code Online (Sandbox Code Playgroud)
第二个:
twoBrowsers = do
b1 <- goto "http://www.google.com"
b2 <- goto "http://www.stackoverflow.com/"
putStr (getHtml b1)
putStr (getHtml b2)
Run Code Online (Sandbox Code Playgroud)
更新(回复您的更新):
如果Browser有一个状态,它可以发送HTTP Referer头和cookie,同时隐藏自身内部的所有机制并提供漂亮的API.
不需要状态仍然goto可以采取浏览器参数.首先,我们需要扩展类型:
data Browser = Browser { getUrl :: String, getHtml :: String, getLinks :: [String],
getCookies :: Map String String } -- keys are URLs, values are cookie strings
getUrlContents :: String -> String -> String -> IO String
getUrlContents url referrer cookies = ...
goto :: String -> Browser -> IO Browser
goto url browser = let
referrer = getUrl browser
cookies = getCookies browser ! url
in
do
html <- getUrlContents url referrer cookies
let links = getLinksFromHtml html
return (Browser url html links)
newBrowser :: Browser
newBrowser = Browser "" "" [] empty
Run Code Online (Sandbox Code Playgroud)
如果浏览器没有状态,开发人员可能会传递所有当前的URL/HTML/Cookies - 这会给场景代码增加噪音.
不,您只需传递类型为Browser的值.以你为例,
useGoogle :: IO ()
useGoogle = do
b <- goto "http://www.google.com/" newBrowser
let b2 = typeIntoInput 0 "haskell" b
b3 <- clickButton "Google Search" b2
...
Run Code Online (Sandbox Code Playgroud)
或者你可以摆脱这些变量:
(>>~) = flip mapM -- use for binding pure functions
useGoogle = goto "http://www.google.com/" newBrowser >>~
typeIntoInput 0 "haskell" >>=
clickButton "Google Search" >>=
clickLink "2" >>=
clickLink "3" >>~
getHtml >>=
putStr
Run Code Online (Sandbox Code Playgroud)
这看起来不错吗?请注意,浏览器仍然是不可变的.