textocat-api-0.1.0.0: src/Network/Textocat/Monadic.hs
{- |
Module : Network.Textocat.Monadic
Description : Monadic binding for Textocat API
Copyright : (c) Mansur Ziatdinov, 2014--2015
License : Apache-2.0
Maintainer : mz+textocat-api@fosslabs.ru
Stability : experimental
Portability : POSIX
There are two kind of functions in this module. Functions that have apostrophe
in their names send data to server every time you call them. Functions without
them call server only if necessary (e.g. you requested it)
This monadic interface is experimental. If you know how to improve it, please
contact author
-}
{-# LANGUAGE DeriveFunctor #-}
module Network.Textocat.Monadic
( -- * Monad wrappers
-- ** General monad transformer
TextocatM
, runTextocatM
-- ** Monad over IO
, Textocat
, runTextocat
-- * API operations with explicit calls
, queue'
, request'
, retrieve'
, search'
, status
-- ** Helpers
, wait'
-- * API operations with implicit calls
, queue
, queue1
, force
, request
, retrieve
) where
import Data.Textocat
import Network.Textocat.Simple
import Control.Applicative
import Control.Arrow
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.RWS
import Data.ByteString (ByteString)
import Data.Either
import Data.Text (Text)
initialSt :: [Document]
initialSt = []
-- | TextocatM is a monad transformer to use Textocat API
-- over some monad @m@ and return result @a@
newtype TextocatM m a = TextocatM
{ unTextocatM :: RWST Config () [Document] m a
} deriving (Functor)
instance (Functor m, Monad m) => Applicative (TextocatM m) where
pure x = TextocatM $ pure x
TextocatM f <*> TextocatM x = TextocatM $ f <*> x
instance (Functor m, MonadIO m) => Monad (TextocatM m) where
return = pure
TextocatM ma >>= f = TextocatM $ ma >>= unTextocatM . f
instance MonadTrans TextocatM where
lift action = TextocatM $ lift action
instance (Functor m, MonadIO m) => MonadIO (TextocatM m) where
liftIO = TextocatM . liftIO
type Textocat = TextocatM IO
-- | Executes TextocatM actions with given API key
runTextocatM :: (Functor m, MonadIO m)
=> ByteString -- ^ API key
-> TextocatM m a -- ^ TextocatM action
-> m a
runTextocatM apiKey ma = fmap fst $ evalRWST (unTextocatM ma) (mkConfig apiKey) initialSt
-- | Executes Textocat actions with given API key
runTextocat :: ByteString -- ^ API key
-> Textocat a -- ^ Textocat action
-> IO a
runTextocat = runTextocatM
-- | Run function that depends on 'Config' in TextocatM monad,
-- reading 'Config' from Reader
withConfig :: (Functor m, MonadIO m)
=> (Config -> b -> IO a) -- ^ Function that depends on Config and something else
-> b -- ^ Something else
-> TextocatM m a
withConfig f b = TextocatM $ ask >>= liftIO . flip f b
-- | Queues given documents. No requests go to server until 'force',
-- 'request' of 'retrieve' is called
queue :: (Functor m, MonadIO m)
=> [Document] -- ^ Documents to queue
-> TextocatM m ()
queue docs = TextocatM $ modify (docs++)
-- | Queues single document. No requests go to server until 'force',
-- 'request' of 'retrieve' is called
queue1 :: (Functor m, MonadIO m)
=> Document -- ^ Document to queue
-> TextocatM m ()
queue1 doc = TextocatM $ modify (doc:)
-- | Queues documents. Actually, makes call to 'entityQueue' with
-- collections divided by 50 documents at most
queue' :: (Functor m, MonadIO m) => [Document] -> TextocatM m [Either ErrorMsg BatchStatus]
queue' docs | null docs = return []
| length docs <= 50 = fmap pure $ withConfig entityQueue docs
| otherwise = do
let (d,rest) = splitAt 50 docs
b <- withConfig entityQueue d
fmap (b:) $ queue' rest
-- | Actually sends documents that were added by 'queue' and 'queue1' calls
-- to the API server.
-- If there are more than 50 documents, they will be sent in several calls,
-- each call will have 50 document at most (API limitation)
force :: (Functor m, MonadIO m) => TextocatM m [Either ErrorMsg BatchStatus]
force = TextocatM $ get >>= unTextocatM . queue'
-- | Requests batch status
request' :: (Functor m, MonadIO m)
=> BatchID -- ^ Batch
-> TextocatM m (Either ErrorMsg BatchStatus)
request' = withConfig entityRequest
-- | Retrieves batches
retrieve' :: (Functor m, MonadIO m)
=> [BatchID] -- ^ Batches
-> TextocatM m (Either ErrorMsg Batch)
retrieve' = withConfig entityRetrieve
-- | Search all collections
search' :: (Functor m, MonadIO m)
=> Text -- ^ Search query
-> TextocatM m (Either ErrorMsg SearchResult)
search' = withConfig entitySearch
-- | Waits while batch processing is finished
wait' :: (Functor m, MonadIO m)
=> BatchID
-> TextocatM m ()
wait' = withConfig waitForFinished
-- | Check service status
status :: (Functor m, MonadIO m) => TextocatM m ServiceStatus
status = TextocatM $ ask >>= liftIO . serviceStatus
-- | Request all documents that were added by 'queue' and 'queue1' calls.
-- Can create several batches, if there are more than 50 documents
request :: (Functor m, MonadIO m) => TextocatM m [Either ErrorMsg BatchStatus]
request = force >>= mapM makeRequest
where makeRequest (Left error) = return $ Left error
makeRequest (Right batchStatus) = request' $ getBatchID batchStatus
-- | Retrieve all documents that were added by 'queue' and 'queue1' calls.
-- Can create several batches, if there are more than 50 documents.
-- Blocks until processing of all batches is finished
retrieve :: (Functor m, MonadIO m) => TextocatM m [Either ErrorMsg Batch]
retrieve = request >>= mapM makeWait >>= uncurry (liftM2 (++)) . ret
where
ret = (return . map Left *** fmap pure . retrieve') . partitionEithers
makeWait (Left error) = return $
Left error
makeWait (Right batchStatus) = let bId = getBatchID batchStatus
in wait' bId >> return (Right bId)