# Pastebin JWvF4erR -- 01. Background: class AWSRequest req where type AWSResponse req :: Type -- Other stuff -- Requests which returns paged results that we know how to stream class AWSRequest req => AWSPager req where -- Stuff -- Amazonka has a bunch of functions which take an `Env` and use it to -- make requests: sendEither :: (AWSRequest req, MonadResource m) => Env -> req -> m (Either Error (AWSResponse req)) paginate :: (AWSPager req, MonadResource m) => Env -> req -> ConduitT Void (AWSResponse req) m () within :: Region -> Env -> Env -- 02. The Problem: -- Abstract over these operations in a way that allows useful alternative implementations (e.g., mocks for testing) -- 03. Solution 1: operations inside typeclass: class Monad m => MonadAmazonka m where sendEither :: AWSRequest req => req -> m (Either Error (AWSResponse req)) paginate :: AWSPager req => req -> ConduitT Void (AWSResponse req) m within :: Region -> m a -> m a newtype AmazonkaT m a = AmazonkaT (ReaderT Env m a) instance MonadResource m => MonadAmazonka (AmazonkaT m) -- Problem: Return type of `paginate` prevents GeneralizedNewtypeDeriving from working. -- 04. Solution 2: Env inside typeclass: class Monad m => MonadAmazonka m where askEnv :: m Env localEnv :: (Env -> Env) -> m a -> m a sendEither :: (AWSRequest req, MonadResource m) => req -> m (Either Error (AWSResponse req)) -- Problem: Mocking doesn't really work as you have all these new constraints on your `m` -- 05. Solution 3: class provides a lift from a base monad: class Monad m => MonadAmazona m where liftAmazonka :: Amazonka a -> m a newtype AmazonkaT m a = AmazonkaT (ReaderT Env m a) type Amazonka = AmazonkaT (ResourceT IO) instance MonadAmazonka Amazonka -- Problem: Mocking is completely impossible since you can't inspect the actual computation. -- 06. Solution 4: use a GADT/free monad/homomorphism typeclass. -- Inspired by: https://blog.ocharles.org.uk/posts/2017-08-23-extensible-effects-and-transformers.html data AmazonkaCall a where SendEither :: AWSRequest a => a -> AmazonkaCall (Either Error (AWSResponse a)) -- etc class MonadAmazonka m where liftAmazonka :: Free (Coyoneda AmazonkaCall) a -> m a -- Problem: It is not possible to write operations like `within` as you get values of type `Free (Coyoneda AmazonkaCall a)` in negative position if you try.