packages feed

co-log-0.7.0.0: tutorials/Main.hs

{-# OPTIONS_GHC -Wno-unused-top-binds #-}

{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DeriveAnyClass             #-}
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs               #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE PatternSynonyms            #-}
{-# LANGUAGE TypeApplications           #-}

module Main (main) where

import Prelude hiding (log)

import Control.Concurrent (threadDelay)
import Control.Exception (Exception)
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Reader (MonadReader, ReaderT (..))

import Colog (HasLog (..), LogAction, Message, Msg (..), PureLogger, RichMsg (..), SimpleMsg (..),
              WithLog, cmap, cmapM, defaultFieldMap, fmtMessage, fmtRichMessageDefault,
              fmtSimpleRichMessageDefault, liftLogIO, log, logException, logInfo, logMessagePure,
              logMsg, logMsgs, logPrint, logStringStdout, logText, logTextStderr, logTextStdout,
              logWarning, pattern D, runPureLog, upgradeMessageAction, usingLoggerT, withLog,
              withLogTextFile, (*<), (<&), (>$), (>$<), (>*), (>*<), (>|<))

import Data.Dependent.Map (delete)
import Type.Reflection (typeRep)

example :: WithLog env Message m => m ()
example = do
    log D "First message..."
    logInfo "Second message..."

simpleApp :: (MonadIO m, WithLog env SimpleMsg m) => m ()
simpleApp = do
    logText "First simple message"
    logText "Second simple message"

app :: (WithLog env Message m, MonadIO m) => m ()
app = do
    logWarning "Starting application..."
    liftIO $ threadDelay $ 10 ^ (6 :: Int)
    withLog (cmap addApp) $ do
        example
        exceptionL
        logInfo "Application finished..."
  where
    addApp :: Message -> Message
    addApp msg = msg { msgText = "app: " <> msgText msg }



data ExampleException = ExampleException
    deriving stock (Show)
    deriving anyclass (Exception)

exceptionL :: (WithLog env Message m) => m ()
exceptionL = logException ExampleException

----------------------------------------------------------------------------
-- Message passing with pipes: &> and <&

-- Remember:
-- (<&) :: LogAction m msg -> msg -> m ()
----------------------------------------------------------------------------

data Collatz = Collatz {even :: Bool, n :: Int, iteration :: Int}

collatz :: LogAction IO String -> Collatz -> IO ()
collatz logger (Collatz False 1 iter) =
    logger <& ("Found 1 after " ++ show iter ++ " iterations")
collatz logger (Collatz even' x iter) = do
    logger <& (show x ++ " on iteration " ++ show iter)
    let newN = if even' then x `div` 2 else 3 * x + 1
    collatz logger $ Collatz (newN `mod` 2 == 0) newN (iter + 1)

----------------------------------------------------------------------------
-- Section with contravariant combinators example
----------------------------------------------------------------------------

data Engine = Pistons Int | Rocket

engineToEither :: Engine -> Either Int ()
engineToEither e = case e of
    Pistons i -> Left i
    Rocket    -> Right ()

data Car = Car
    { carMake   :: String
    , carModel  :: String
    , carEngine :: Engine
    }

carToTuple :: Car -> (String, (String, Engine))
carToTuple (Car make model engine) = (make, (model, engine))

stringL :: LogAction IO String
stringL = logStringStdout

-- Returns log action that logs given string ignoring its input.
constL :: String -> LogAction IO a
constL s = s >$ stringL

intL :: LogAction IO Int
intL = logPrint

-- log actions that logs single car module
carL :: LogAction IO Car
carL = carToTuple
    >$< (constL "Logging make..." *< stringL >* constL "Finished logging make...")
    >*< (constL "Logging model.." *< stringL >* constL "Finished logging model...")
    >*< ( engineToEither
      >$< constL "Logging pistons..." *< intL
      >|< constL "Logging rocket..."
        )

----------------------------------------------------------------------------
-- Custom monad and logger actions of different types
----------------------------------------------------------------------------

data Env m = Env
    { envLogString :: LogAction m String
    , envLogInt    :: LogAction m Int
    }

instance HasLog (Env m) String m where
    getLogAction :: Env m -> LogAction m String
    getLogAction = envLogString

    setLogAction :: LogAction m String -> Env m -> Env m
    setLogAction newAction env = env { envLogString = newAction }

instance HasLog (Env m) Int m where
    getLogAction :: Env m -> LogAction m Int
    getLogAction = envLogInt

    setLogAction :: LogAction m Int -> Env m -> Env m
    setLogAction newAction env = env { envLogInt = newAction }

newtype FooM a = FooM
    { runFooM :: ReaderT (Env FooM) IO a
    } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader (Env FooM))

usingFooM :: Env FooM -> FooM a -> IO a
usingFooM env = flip runReaderT env . runFooM

foo :: (WithLog env String m, WithLog env Int m) => m ()
foo = do
    logMsg ("String message..." :: String)
    logMsg @Int 42

logFoo :: IO ()
logFoo = usingFooM env foo
  where
    env :: Env FooM
    env = Env
        { envLogString = liftLogIO logStringStdout
        , envLogInt    = liftLogIO logPrint
        }

----------------------------------------------------------------------------
-- main runner
----------------------------------------------------------------------------

main :: IO ()
main = withLogTextFile "tutorials/example.log" $ \logTextFile -> do
    let runApp :: LogAction IO Message -> IO ()
        runApp action = usingLoggerT action app

    let runSimpleApp :: LogAction IO SimpleMsg -> IO ()
        runSimpleApp action = usingLoggerT action simpleApp

    let textAction = logTextStdout <> logTextStderr <> logTextFile

    let simpleMessageAction = cmap  fmtMessage            textAction
    let richMessageAction   = cmapM fmtRichMessageDefault textAction

    let richSimpleAction :: LogAction IO (RichMsg IO SimpleMsg)
        richSimpleAction = cmapM fmtSimpleRichMessageDefault textAction

    let simpleAction :: LogAction IO SimpleMsg
        simpleAction = upgradeMessageAction defaultFieldMap richSimpleAction

    let fullMessageAction = upgradeMessageAction defaultFieldMap richMessageAction
    let semiMessageAction = upgradeMessageAction
                                (delete (typeRep @"threadId") defaultFieldMap)
                                richMessageAction

    runApp simpleMessageAction
    runApp fullMessageAction
    runApp semiMessageAction

    runSimpleApp simpleAction

    usingLoggerT carL $ logMsg $ Car "Toyota" "Corolla" (Pistons 4)

    let pureAction :: PureLogger Message ()
        pureAction = usingLoggerT logMessagePure example
    let ((), msgs) = runPureLog pureAction
    usingLoggerT simpleMessageAction $ logMsgs msgs

    logFoo