|
| 1 | +module App.Production.Async where |
| 2 | +-- Layers One and Two have to be in same file due to orphan instance restriction |
| 3 | + |
| 4 | +import Prelude |
| 5 | + |
| 6 | +import App.Types (Name(..)) |
| 7 | +import App.Application (class Logger, class GetUserName) |
| 8 | +import Control.Monad.Reader (class MonadAsk, ReaderT, ask, asks, runReaderT) |
| 9 | +import Effect.Aff (Aff, Milliseconds(..), delay) |
| 10 | +import Effect.Aff.Class (class MonadAff, liftAff) |
| 11 | +import Effect.Class (class MonadEffect, liftEffect) |
| 12 | +import Effect.Class.Console (log) as Console |
| 13 | +import Node.Encoding (Encoding(..)) |
| 14 | +import Node.FS.Aff (readTextFile) as Async |
| 15 | +import Type.Equality (class TypeEquals, from) |
| 16 | + |
| 17 | +-- | Layer 2 Define our "Production" Monad but using Aff... |
| 18 | +type Environment = { asyncEnv :: String } |
| 19 | +newtype AppMA a = AppMA (ReaderT Environment Aff a) |
| 20 | + |
| 21 | +-- | ...and the means to run computations in it |
| 22 | +runApp :: forall a. AppMA a -> Environment -> Aff a |
| 23 | +runApp (AppMA reader_T) env = runReaderT reader_T env |
| 24 | + |
| 25 | +-- | Layer 1 Production in Aff |
| 26 | +derive newtype instance functorAppMA :: Functor AppMA |
| 27 | +derive newtype instance applyAppMA :: Apply AppMA |
| 28 | +derive newtype instance applicativeAppMA :: Applicative AppMA |
| 29 | +derive newtype instance bindAppMA :: Bind AppMA |
| 30 | +derive newtype instance monadAppMA :: Monad AppMA |
| 31 | +derive newtype instance monadEffectAppMA :: MonadEffect AppMA |
| 32 | +derive newtype instance monadAffAppMA :: MonadAff AppMA |
| 33 | + |
| 34 | +-- | Reader instance not quite as simple a derivation as "derive newtype", |
| 35 | +-- | as it needs TypeEquals for the env |
| 36 | +instance monadAskAppMA :: TypeEquals e Environment => MonadAsk e AppMA where |
| 37 | + ask = AppMA $ asks from |
| 38 | + |
| 39 | +-- | implementing Logger here just to the console, but in real world you'd use |
| 40 | +-- | the available Env to determine log levels, output destination, DB handles etc |
| 41 | +-- | because this version runs in Aff you can do Aff-ish things here (not shown) |
| 42 | +instance loggerAppMA :: Logger AppMA where |
| 43 | + log = liftEffect <<< Console.log |
| 44 | + |
| 45 | +-- | a version of getUserName that reads the name from a file |
| 46 | +-- | given in the Environment |
| 47 | +instance getUserNameAppMA :: GetUserName AppMA where |
| 48 | + getUserName = do |
| 49 | + env <- ask -- we still have access to underlying ReaderT |
| 50 | + liftAff do -- but we can also run computations in Aff |
| 51 | + delay $ Milliseconds 1000.0 -- 1 second |
| 52 | + contents <- Async.readTextFile UTF8 env.asyncEnv |
| 53 | + pure $ Name $ contents |
0 commit comments