heddit-0.0.3: examples/Paginating.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedLabels #-}
-- | This example program shows how to use 'Paginator's and 'Listing's, which
-- are used in many API endpoints. You may notice that several of the actions
-- exported by this library have a type signature similar to
-- @... Paginator t a -> Listing t a@. In many cases, when you make a request to
-- Reddit, it will respond with a \"view\" of the data along with controls that
-- allow you to paginate through entries. This is represented as the 'Listing'
-- type in @heddit@. You can use this listing to feed the next action to fetch
-- more items in the form of a 'Paginator'. In addition, many of the types that
-- are 'Paginable' (and thus can be used in a 'Paginator') have extra options
-- that can be passed to the 'Paginator' in order to filter, limit or sort items.
--
-- There are are a couple of ways to deal with 'Listing's and 'Paginator's in
-- @heddit@, as the examples below aim to illustrate
module Paginating where
import Data.Foldable ( for_ )
import Data.Generics.Labels ()
import qualified Data.Text.IO as T
import Lens.Micro.Platform
import Network.Reddit
main :: IO ()
main = do
c <- loadClient Nothing
subname <- mkSubredditName "haskell"
results <- runReddit c . sequence
$ [ ignorePagination, withOptions, secondPage ] <*> [ subname ]
for_ (zip descs results)
$ \(desc, res) -> T.putStrLn $ desc <> ": " <> getTitle res
where
descs = [ "1st result (without pagination)"
, "1st result (all-time)"
, "11th result (with pagination)"
]
getTitle = maybe "No results!" (^. #title)
-- | If you do not want to deal with any of this 'Listing' or 'Paginator',
-- business, but would rather just get the first results that Reddit returns,
-- you can use the convenience function 'firstPage' with your action. This
-- ignores the pagination controls entirely, and just gets the items from the
-- endpoint. You can\'t fetch subsequent items, however, or pass any additional
-- options. You are thus limited to the first 100 items, with default options
ignorePagination :: MonadReddit m => SubredditName -> m (Maybe Submission)
ignorePagination subname =
-- 'firstPage' just gets up to the first 100 items, using the default
-- sort and other options
firstPage (getTopSubmissions subname) <&> (^? _head)
-- | This example uses a 'Paginator' to add an option telling Reddit the timeframe
-- we are interested in.
--
-- With actions that take a @Paginator@ and return a @Listing@, you can
-- provide an initial paginator with @emptyPaginator@. @Paginator@ has
-- a field @opts@ that holds an instance of the 'PaginateOptions' type
-- family. Check the haddocks for the options of different 'Paginable'
-- types
withOptions :: MonadReddit m => SubredditName -> m (Maybe Submission)
withOptions subname = do
-- In this case, we specify the time range that we are interested in
submissions <- getTopSubmissions subname
$ emptyPaginator & #opts . #itemTime ?~ AllTime
-- @submissions@ is a @Listing SubmissionID Submission@. The @children@
-- field of a listing holds the actual results. The other fields will
-- be discussed below
pure $ submissions ^? #children . _head
-- | In this example, we can use the 'Listing' resulting from an action to get
-- the next \"page\" of results
secondPage :: MonadReddit m => SubredditName -> m (Maybe Submission)
secondPage subname = do
-- All @Paginator@s have a @limit@ field to specify the number of items
-- desired, which we will set to 10 (the maximum is ostensibly 100,
-- although Reddit doesn't seem to care if you exceed this; the default
-- is 25)
firstListing <- getTopSubmissions subname firstPaginator
-- Now that we have a listing, we can turn it into a @Paginator@ to feed
-- the next API call by using the function 'nextPage'. You can optionally
-- provide the initial paginator as well to avoid having to set the same
-- options again.
--
-- All @Listing@s have @before@ and @after@ fields which act like anchors
-- in the "slice" of data. They can be used as pagination controls, but are
-- not necessarily present. Using @nextPage@ will create a @Paginator@ with
-- corresponding @before@ and @after@ fields set to the values from the
-- @Listing@. When both fields are @Just@, @after@ takes precedence.
--
-- The behavior of @before@ is a little counter-intuitive (at least to me).
-- It does /not/ point to data preceding the current first item in the
-- @children@ field, and will only be non-null if items have been created
-- that would precede it in the interim since obtaining the @Listing@. That
-- is, even if you are on the nth page above 1 of the data set, @before@ will
-- still be @Nothing@ unless entirely new items have been created on
-- Reddit's end
nextListing <- getTopSubmissions subname
$ nextPage (Just firstPaginator) firstListing
-- We could continue in the same vein, but we'll stop here and return the
-- first child
pure $ nextListing ^? #children . _head
where
firstPaginator = emptyPaginator & #limit .~ 10