HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Moduleco-log-core-0.3.2.2Haskell2010

Colog.Core.Action

SPDX-License-Identifier : MPL-2.0 Maintainer : Co-Log xrom.xkov@gmail.com Stability : Stable Portability : Portable

Implements core data types and combinators for logging actions.

  • 1 type
  • 29 values

Core type and instances

3 declarations
newtypenewtype LogAction (m :: Type -> Type) msg
#

Polymorphic and very general logging action type.

  • msg type variables is an input for logger. It can be Text or custom logging messsage with different fields that you want to format in future.

  • m type variable is for monadic action inside which logging is happening. It can be either IO or some custom pure monad.

Key design point here is that LogAction is:

Constructors

Instances5Functor, Contravariant, Semigroup, Monoid, HasLog
  • UnrepresentableClass => Functor (LogAction m)Defined in co-log-core-0.3.2.2 · Colog.Core.Action

    ⚠️CAUTION⚠️ This instance is for custom error display only.

    LogAction is not supposed to have Functor instance by design.

    In case it is used by mistake, the user will see the following:

    Example1 expression
    fmap show logStringStdout...... 'LogAction' cannot have a 'Functor' instance by design.      However, you've attempted to use this instance....      Probably you meant 'Contravariant' class instance with the following methods:        * contramap :: (a -> b) -> LogAction m b -> LogAction m a        * (>$) :: b -> LogAction m b -> LogAction m a...

    # 207 "srcCologCore/Action.hs"

  • Contravariant (LogAction m)Defined in co-log-core-0.3.2.2 · Colog.Core.Action
  • Applicative m => Semigroup (LogAction m a)Defined in co-log-core-0.3.2.2 · Colog.Core.Action

    This instance allows you to join multiple logging actions into single one.

    For example, if you have two actions like these:

    logToStdout :: LogAction IO String  -- outputs String to terminal
    logToFile   :: LogAction IO String  -- appends String to some file
    

    You can create new LogAction that perform both actions one after another using Semigroup:

    logToBoth :: LogAction IO String  -- outputs String to both terminal and some file
    logToBoth = logToStdout <> logToFile
    
  • Applicative m => Monoid (LogAction m a)Defined in co-log-core-0.3.2.2 · Colog.Core.Action
  • HasLog (LogAction m msg) msg mDefined in co-log-core-0.3.2.2 · Colog.Core.Class
value(<&) :: LogAction m msg -> msg -> m ()
#

Operator version of unLogAction. Note that because of the types, something like:

action <& msg1 <& msg2

doesn't make sense. Instead you want:

action <& msg1 >> action <& msg2

In addition, because <& has higher precedence than the other operators in this module, the following:

f >$< action <& msg

is equivalent to:

(f >$< action) <& msg
value(&>) :: msg -> LogAction m msg -> m ()
#

A flipped version of <&.

It shares the same precedence as <&, so make sure to surround lower precedence operators in parentheses:

msg &> (f >$< action)

Semigroup combinators

1 declaration

Contravariant combinators

8 declarations

Combinators that implement interface in the spirit of the following typeclass:

class Contravariant f where
    contramap :: (a -> b) -> f b -> f a
valuecfilterM :: Monad m => (msg -> m Bool) -> LogAction m msg -> LogAction m msg
#

Performs the given logging action only if satisfies the monadic predicate. Let's say you want to only to see logs that happened on weekends.

isWeekendM :: MessageWithTimestamp -> IO Bool

And use it with cfilterM like this

logMessageAction :: LogAction m MessageWithTimestamp

logWeekendAction :: LogAction m MessageWithTimestamp
logWeekendAction = cfilterM isWeekendM logMessageAction
valuecmap :: (a -> b) -> LogAction m b -> LogAction m a
#

This combinator is contramap from contravariant functor. It is useful when you have something like

data LogRecord = LR
    { lrName    :: LoggerName
    , lrMessage :: Text
    }

and you need to provide LogAction which consumes LogRecord

logRecordAction :: LogAction m LogRecord

when you only have action that consumes Text

logTextAction :: LogAction m Text

With cmap you can do the following:

logRecordAction :: LogAction m LogRecord
logRecordAction = cmap lrMesssage logTextAction

This action will print only lrMessage from LogRecord. But if you have formatting function like this:

formatLogRecord :: LogRecord -> Text

you can apply it instead of lrMessage to log formatted LogRecord as Text.

value(>$<) :: (a -> b) -> LogAction m b -> LogAction m a
#

Operator version of cmap.

Example1 expression
1 &> (show >$< logStringStdout)1
value(>$) :: b -> LogAction m b -> LogAction m a
#

This combinator is >$ from contravariant functor. Replaces all locations in the output with the same value. The default definition is contramap . const, so this is a more efficient version.

Example2 expressions
"Hello?" &> ("OUT OF SERVICE" >$ logStringStdout)OUT OF SERVICE("OUT OF SERVICE" >$ logStringStdout) <& 42OUT OF SERVICE
valuecmapM :: Monad m => (a -> m b) -> LogAction m b -> LogAction m a
#

cmapM combinator is similar to cmap but allows to call monadic functions (functions that require extra context) to extend consumed value. Consider the following example.

You have this logging record:

data LogRecord = LR
    { lrTime    :: UTCTime
    , lrMessage :: Text
    }

and you also have logging consumer inside IO for such record:

logRecordAction :: LogAction IO LogRecord

But you need to return consumer only for Text messages:

logTextAction :: LogAction IO Text

If you have function that can extend Text to LogRecord like the function below:

withTime :: Text -> IO LogRecord
withTime msg = do
    time <- getCurrentTime
    pure (LR time msg)

you can achieve desired behavior with cmapM in the following way:

logTextAction :: LogAction IO Text
logTextAction = cmapM withTime myAction

Divisible combinators

6 declarations

Combinators that implement interface in the spirit of the following typeclass:

class Contravariant f => Divisible f where
    conquer :: f a
    divide  :: (a -> (b, c)) -> f b -> f c -> f a
valuedivide
  1. :: Applicative m
  2. => a -> (b, c)
  3. -> LogAction m b
  4. -> LogAction m c
  5. -> LogAction m a
#

divide combinator from Divisible type class.

Example2 expressions
logInt = LogAction print"ABC" &> divide (\s -> (s, length s)) logStringStdout logIntABC3
valueconquer :: Applicative m => LogAction m a
#

conquer combinator from Divisible type class.

Concretely, this is a LogAction that does nothing:

Example2 expressions
conquer <& "hello?""hello?" &> conquer
value(>*<)
  1. :: Applicative m
  2. => LogAction m a
  3. -> LogAction m b
  4. -> LogAction m (a, b)
#

Operator version of divide id.

Example3 expressions
logInt = LogAction print(logStringStdout >*< logInt) <& ("foo", 1)foo1(logInt >*< logStringStdout) <& (1, "foo")1foo
value(>*) :: Applicative m => LogAction m a -> LogAction m () -> LogAction m a
#

Perform a constant log action after another.

Example2 expressions
logHello = LogAction (const (putStrLn "Hello!"))"Greetings!" &> (logStringStdout >* logHello)Greetings!Hello!

Decidable combinators

4 declarations

Combinators that implement interface in the spirit of the following typeclass:

class Divisible f => Decidable f where
    lose   :: (a -> Void) -> f a
    choose :: (a -> Either b c) -> f b -> f c -> f a
valuechoose
  1. :: a -> Either b c
  2. -> LogAction m b
  3. -> LogAction m c
  4. -> LogAction m a
#

choose combinator from Decidable type class.

Example4 expressions
logInt = LogAction printf = choose (\a -> if a < 0 then Left "Negative" else Right a)f logStringStdout logInt <& 11f logStringStdout logInt <& (-1)Negative
value(>|<) :: LogAction m a -> LogAction m b -> LogAction m (Either a b)
#

Operator version of choose id.

Example3 expressions
dontPrintInt = LogAction (const (putStrLn "Not printing Int"))Left 1 &> (dontPrintInt >|< logStringStdout)Not printing Int(dontPrintInt >|< logStringStdout) <& Right ":)":)

Comonadic combinators

7 declarations

Combinators that implement interface in the spirit of the following typeclass:

class Functor w => Comonad w where
    extract   :: w a -> a
    duplicate :: w a -> w (w a)
    extend    :: (w a -> b) -> w a -> w b
valueextract :: Monoid msg => LogAction m msg -> m ()
#

If msg is Monoid then extract performs given log action by passing mempty to it.

Example2 expressions
logPrint :: LogAction IO [Int]; logPrint = LogAction printextract logPrint[]
valueextend
  1. :: Semigroup msg
  2. => LogAction m msg -> m ()
  3. -> LogAction m msg
  4. -> LogAction m msg
#

This is a comonadic extend. It allows you to chain different transformations on messages.

Example6 expressions
f (LogAction l) = l ".f1" *> l ".f2"g (LogAction l) = l ".g"logStringStdout <& "foo"fooextend f logStringStdout <& "foo"foo.f1foo.f2(extend g $ extend f logStringStdout) <& "foo"foo.g.f1foo.g.f2(logStringStdout =>> f =>> g) <& "foo"foo.g.f1foo.g.f2
valueduplicate :: Semigroup msg => LogAction m msg -> LogAction m (msg, msg)
#

Converts any LogAction that can log single message to the LogAction that can log two messages. The new LogAction behaves in the following way:

  1. Joins two messages of type msg using <> operator from Semigroup.

  2. Passes resulted message to the given LogAction.

Example1 expression
:{let logger :: LogAction IO [Int]    logger = logPrintin duplicate logger <& ([3, 4], [42, 10]):}[3,4,42,10]

Implementation note:

True and fair translation of the duplicate function from the Comonad interface should result in the LogAction of the following form:

msg -> msg -> m ()

In order to capture this behavior, duplicate should have the following type:

duplicate :: Semigroup msg => LogAction m msg -> LogAction (Compose ((->) msg) m) msg

However, it's quite awkward to work with such type. It's a known fact that the following two types are isomorphic (see functions curry and uncurry):

a -> b -> c
(a, b) -> c

So using this fact we can come up with the simpler interface.

valuemultiplicate
  1. :: (Foldable f, Monoid msg)
  2. => LogAction m msg
  3. -> LogAction m (f msg)
#

Like duplicate but why stop on a pair of two messages if you can log any Foldable of messages?

Example1 expression
:{let logger :: LogAction IO [Int]    logger = logPrintin multiplicate logger <& replicate 5 [1..3]:}[1,2,3,1,2,3,1,2,3,1,2,3,1,2,3]

Higher-order combinators

1 declaration
valuehoistLogAction :: (forall x. m x -> n x) -> LogAction m a -> LogAction n a
#

Allows changing the internal monadic action.

Let's say we have a pure logger action using PureLogger and we want to log all messages into IO instead.

If we provide the following function:

performPureLogsInIO :: PureLogger a -> IO a

then we can convert a logger action that uses a pure monad to a one that performs the logging in the IO monad using:

hoistLogAction performPureLogsInIO :: LogAction (PureLogger a) a -> LogAction IO a