我正在用Haskell编写某种刮板或数据挖掘器。它包含一个主循环和其他共享逻辑,以及多个“适配器”,每个“适配器”旨在抓取特定类型的资源(不仅是网页,而且还可能是文件系统对象)。适配器都产生相同类型的结果,但我希望它们彼此独立。另外,我希望他们共享主循环和其他逻辑。
到目前为止,这是我使用ExistentialQuantification来隐藏刮除作业的适配器从属关系的结果。我的想法是,主循环处理一系列作业,并在“ process”方法上调度以找到正确的适配器实现。
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ExistentialQuantification #-}
import Control.Monad.Trans.Except
import Data.Text (Text)
import qualified Data.Text as T
-- Metadata about an article
data Article = Article
{ articleSource :: Text
, articleTitle :: Text
, articleUrl :: Text
, articleText :: Text
, articleDate :: Maybe Text
}
deriving (Show)
-- Adapter for scraping a certain type of resource.
--
-- The adapter provides a seed job constructor which samples the current config
-- and creates a seed job to be run first. The seed job then generates all
-- other jobs, e.g. by scraping an index page.
--
--- Each adapter module exports a value of this type, and nothing else.
data Adapter = Adapter
{ adapterName :: Text
, adapterSeedJob :: ScraperConfig -> AnyJob
}
-- Each scrape operation produces zero or more articles, and zero or more
-- new scrape jobs.
type ScrapeResult j = ExceptT AdapterErr IO ([Article], [j])
-- Specification of a resource to be scraped by a certain adapter.
--
-- Each adapter defines its own job type, containing the necessary information.
class ScrapeJob j where
jobAdapter :: j -> Text
jobDesc :: j -> Text
jobProcess :: j -> ScrapeResult j
-- Opaque type encapsulating any adapter's job type, used outside the adapter.
data AnyJob = forall j. ScrapeJob j => AnyJob j
instance ScrapeJob AnyJob where
jobAdapter (AnyJob j) = jobAdapter j
jobDesc (AnyJob j) = jobDesc j
jobProcess (AnyJob j) = wrap <$> jobProcess j
where wrap (as, js) = (as, AnyJob <$> js)
-- Global configuration, such as which passwords to use
data ScraperConfig = ScraperConfig
{ -- ...
}
我的问题是每个适配器也都附加了一些上下文。一个示例是,大多数适配器需要先执行某种登录过程,然后才能访问任何数据。我希望在可能的情况下与抓取本身分开处理,但是我无法将AnyJob的技巧用于“ AnyContext”(我认为),因为无法保证主循环的类型AnyJob和AnyContext正确匹配。
我目前对我都不满意的想法是:
将上下文的类型固定为“数据包”(例如“地图文本”),并向创建该上下文的每个adpater添加特殊的设置方法。
将所需的上下文添加为每个ScrapeJob实例的字段,并在创建新作业时显式复制它。
将设计上下颠倒,让每个适配器使用实用程序模块中定义的共享功能运行自己的主循环。
我这里缺少什么吗?有关如何改进此设计的任何建议将不胜感激。
谢谢!