Haskell API客户端分页

时间:2015-08-22 00:41:39

标签: rest haskell

我正在尝试构建一个Haskell客户端来使用RESTful JSON API。我想获取一个页面,然后从响应中获取“next_page”键,并将其提供给另一个api请求的查询参数。我想继续这样做,直到我浏览了所有项目。在Haskell中最干净的方法是什么?我现在正在使用显式递归,但我觉得必须有更好的方法,可能是作家或州monad。

编辑:添加了我的代码。我知道我是在误用。

data Post = Post {
          title  :: !Text,
          domain :: !Text,
          score  :: Int,
          url    :: !Text
} deriving (Show)

instance FromJSON Post where
  parseJSON (Object v) = do
    objectData <- v .: "data"
    title  <- objectData .: "title"
    domain <- objectData .: "domain"
    score  <- objectData .: "score"
    url    <- objectData .: "url"
    return $ Post title domain score url

data GetPostsResult = GetPostsResult {
  posts :: [Post],
  after :: Maybe Text
} deriving (Show)

instance FromJSON GetPostsResult where
  parseJSON (Object v) = do
    rootData   <- v        .:  "data"
    posts      <- rootData .:  "children"
    afterCode <- rootData .:? "after"
    return $ GetPostsResult posts afterCode

fetchPage:: Text -> IO (Maybe ([Post],Maybe Text))
fetchPage afterCode = do
  let url = "https://www.reddit.com/r/videos/top/.json?sort=top&t=day&after=" ++ unpack afterCode
  b <- get url
  let jsonBody = b ^. responseBody
  let postResponse = decode jsonBody :: Maybe GetPostsResult
  let pagePosts = posts <$>  postResponse
  let nextAfterCode = after $ fromJust postResponse
  if isNothing pagePosts then return Nothing else return (Just (fromJust pagePosts,nextAfterCode))

getPosts :: Text -> [Post] -> IO [Post]
getPosts x y = do
  p <- liftIO $ fetchPage x
  let posts = fst (fromJust p)
  let afterParam =  snd (fromJust p)
  case afterParam of
    Nothing ->  return []
    Just aff -> getPosts aff (posts ++ y)

main = do
  a <- getPosts "" []
  print a

2 个答案:

答案 0 :(得分:2)

由于它的简单性,你的方法肯定很有吸引力,但是它有一个缺点,Post的列表只有在整个分页链结束后才可用。

我建议使用像PipeConduit和朋友这样的流媒体库。优点是您可以通过使用相应流媒体库提供的功能来流式传输结果并限制要检索的帖子数量。以下是Pipe的示例,我添加了必要的导入并添加了postsP

{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Data.Text (Text)
import qualified Data.Text as T
import Network.Wreq
import Data.Maybe
import Control.Lens
import Control.Monad.IO.Class
import qualified Pipes as P
import Data.Foldable (for_)

data Post = Post {
          title  :: !Text,
          domain :: !Text,
          score  :: Int,
          url    :: !Text
} deriving (Show)

instance FromJSON Post where
  parseJSON (Object v) = do
    objectData <- v .: "data"
    title  <- objectData .: "title"
    domain <- objectData .: "domain"
    score  <- objectData .: "score"
    url    <- objectData .: "url"
    return $ Post title domain score url

data GetPostsResult = GetPostsResult {
  posts :: [Post],
  after :: Maybe Text
} deriving (Show)

instance FromJSON GetPostsResult where
  parseJSON (Object v) = do
    rootData   <- v        .:  "data"
    posts      <- rootData .:  "children"
    afterCode <- rootData .:? "after"
    return $ GetPostsResult posts afterCode

fetchPage:: Text -> IO (Maybe ([Post],Maybe Text))
fetchPage afterCode = do
  let url = "https://www.reddit.com/r/videos/top/.json?sort=top&t=day&after=" ++ T.unpack afterCode
  b <- get url
  let jsonBody = b ^. responseBody
  let postResponse = decode jsonBody :: Maybe GetPostsResult
  let pagePosts = posts <$>  postResponse
  let nextAfterCode = after $ fromJust postResponse
  if isNothing pagePosts then return Nothing else return (Just (fromJust pagePosts,nextAfterCode))

getPosts :: Text -> [Post] -> IO [Post]
getPosts x y = do
  p <- liftIO $ fetchPage x
  let posts = fst (fromJust p)
  let afterParam =  snd (fromJust p)
  case afterParam of
    Nothing ->  return []
    Just aff -> getPosts aff (posts ++ y)

postsP :: Text -> P.Producer Post IO ()
postsP x = do
  p <- liftIO (fetchPage x)
  for_ p $ \(posts,afterParam) -> do
    P.each posts
    for_ afterParam postsP

main = P.runEffect $ P.for (postsP "") (liftIO . print)

答案 1 :(得分:1)

我建议你需要一个延续monad。在延续中,您可以拥有组装页面的逻辑,将其输出到延续的调用者,然后重复。当您调用“get_pages”函数时,您将返回第一页和一个带有新参数的新函数。

听起来你需要this answer,它展示了如何创建这样的monad。如果你的函数中需要任何其他monadic状态,请将它变为monad变换器。