HORIZON HASKELLDocslts/ghc-9.14.xd82dee82026-10-12Search names, modules, packages, or :: a typeCtrl K

GHC 9.14.1 · lts/ghc-9.14.x · d82dee8 · 2026-10-12

Modulelsm-tree-1.1.1.0

Database.LSMTree

  • 52 types
  • 4 classes
  • 85 values
newtypenewtype Tracer (m :: Type -> Type) a
#

This type describes some effect in m which depends upon some value of type a, for which the output value is not of interest (only the effects).

The motivating use case is to describe tracing, logging, monitoring, and similar features, in which the programmer wishes to provide some values to some other program which will do some real world side effect, such as writing to a log file or bumping a counter in some monitoring system.

The actual implementation of such a program will probably work on rather large, domain-agnostic types like Text, ByteString, JSON values for structured logs, etc.

But the call sites which ultimately invoke these implementations will deal with smaller, domain-specific types that concisely describe events, metrics, debug information, etc.

This difference is reconciled by the Contravariant instance for Tracer. contramap is used to change the input type of a tracer. This allows for a more general tracer to be used where a more specific one is expected.

Intuitively: if you can map your domain-specific type Event to a Text representation, then any Tracer m Text can stand in where a Tracer m Event is required.

eventToText :: Event -> Text

traceTextToLogFile :: Tracer m Text

traceEventToLogFile :: Tracer m Event
traceEventToLogFile = contramap eventToText traceTextToLogFile

Effectful tracers that actually do interesting stuff can be defined using emit, and composed via contramap.

The nullTracer can be used as a stand-in for any tracer, doing no side-effects and producing no interesting value.

To deal with branching, the arrow interface on the underlying Tracer should be used. Arrow notation can be helpful here.

For example, a common pattern is to trace only some variants of a sum type.

data Event = This Int | That Bool

traceOnlyThat :: Tracer m Int -> Tracer m Bool
traceOnlyThat tr = Tracer $ proc event -> do
  case event of
    This i -> use tr  -< i
    That _ -> squelch -< ()

The key point of using the arrow representation we have here is that this tracer will not necessarily need to force event: if the input tracer tr does not force its value, then event will not be forced. To elaborate, suppose tr is nullTracer. Then this expression becomes

classify (This i) = Left i
classify (That _) = Right ()

traceOnlyThat tr
= Tracer $ Pure classify >>> (squelch ||| squelch) >>> Pure (either id id)
= Tracer $ Pure classify >>> Pure (either (const (Left ())) (const (Right ()))) >>> Pure (either id id)
= Tracer $ Pure (classify >>> either (const (Left ())) (const (Right ())) >>> either id id)

So that when this tracer is run by traceWith we get

traceWith (traceOnlyThat tr) x
= traceWith (Pure _)
= pure ()

It is _essential_ that the computation of the tracing effects cannot itself have side-effects, as this would ruin the ability to short-circuit when it is known that no tracing will be done: the side-effects of a branch could change the outcome of another branch. This would fly in the face of a crucial design goal: you can leave your tracer calls in the program so they do not bitrot, but can also make them zero runtime cost by substituting nullTracer appropriately.

Instances3Contravariant, Semigroup, Monoid
  • Monad m => Contravariant (Tracer m)Defined in contra-tracer-0.2.1.1 · Control.Tracer
  • Monad m => Semigroup (Tracer m s)Defined in contra-tracer-0.2.1.1 · Control.Tracer

    tr1 <> tr2 will run tr1 and then tr2 with the same input.

  • Monad m => Monoid (Tracer m s)Defined in contra-tracer-0.2.1.1 · Control.Tracer
datadata TableTrace
#
datadata Entry k v b
#

Constructors

Instances7Bifunctor, Functor, Foldable, Traversable, Eq, Show, …
datadata LookupResult v b
#

Constructors

Instances7Bifunctor, Functor, Foldable, Traversable, Eq, Show, …