{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE GADTs #-}
-- | This module contains a mechanisms to specify what kind of messages a
-- process can receive and possible answer by sending a message back to the
-- orginator.
--
-- A message can be either a blocking or a non-blocking request.
--
-- The type magic in the 'Api' type familiy allows to define a related set of messages along
-- with the corresponding responses.
--
-- A process can /serve/ a specific 'Api' instance by using the functions provided by
-- the "Control.Eff.Concurrent.Api.Server" module.
--
-- To enable a process to use such a /service/, the functions provided by
-- the "Control.Eff.Concurrent.Api.Client" should be used.
--
module Control.Eff.Concurrent.Api
( Api
, Synchronicity(..)
, Server(..)
, fromServer
, proxyAsServer
, asServer
)
where
import Data.Kind
import Control.Lens
import Data.Typeable (Typeable, typeRep)
import Control.Eff.Concurrent.Process
-- | This data family defines an API, a communication interface description
-- between to processes, where one process acts as a __server__ and the other(s)
-- as __client(s)__.
--
-- The first parameter is usually a user defined phantom type that identifies
-- the 'Api' instance.
--
-- The second parameter specifies if a specific constructor of an (GADT-like)
-- @Api@ instance is 'Synchronous', i.e. returns a result and blocks the caller
-- or if it is 'Asynchronous'
--
-- Example:
--
-- >
-- > data BookShop deriving Typeable
-- >
-- > data instance Api BookShop r where
-- > RentBook :: BookId -> Api BookShop ('Synchronous (Either RentalError RentalId))
-- > BringBack :: RentalId -> Api BookShop 'Asynchronous
-- >
-- > type BookId = Int
-- > type RentalId = Int
-- > type RentalError = String
-- >
data family Api (api :: Type) (reply :: Synchronicity)
-- | This __data kind__ is used to indicate at the type level if a specific
-- constructor of an @Api@ instance has a result for which some caller has to
-- wait, or if it is asynchronous.
data Synchronicity =
Synchronous Type -- ^ Blocking operation with a specific return type, e.g. @('Synchronous (Either RentalError RentalId))@
| Asynchronous -- ^ Non-blocking async operation
deriving (Typeable)
-- | This is a tag-type that wraps around a 'ProcessId' and holds an 'Api' index
-- type.
newtype Server api = Server { _fromServer :: ProcessId }
deriving (Eq,Ord,Typeable)
instance Read (Server api) where
readsPrec _ ('[':'#':rest1) =
case reads (dropWhile (/= '#') rest1) of
[(c, ']':rest2)] -> [(Server c, rest2)]
_ -> []
readsPrec _ _ = []
instance Typeable api => Show (Server api) where
show s@(Server c) =
"[#" ++ show (typeRep s) ++ "#" ++ show c ++ "]"
makeLenses ''Server
-- | Tag a 'ProcessId' with an 'Api' type index to mark it a 'Server' process
-- handling that API
proxyAsServer :: proxy api -> ProcessId -> Server api
proxyAsServer = const Server
-- | Tag a 'ProcessId' with an 'Api' type index to mark it a 'Server' process
-- handling that API
asServer :: forall api . ProcessId -> Server api
asServer = Server