packages feed

miso-1.13.0.0: src/Miso/Trace.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Trace
-- Copyright   :  (C) 2016-2026 David M. Johnson
-- License     :  BSD3-style (see the file LICENSE)
-- Maintainer  :  David M. Johnson <code@dmj.io>
-- Stability   :  experimental
-- Portability :  non-portable
--
-- = Overview
--
-- "Miso.Trace" provides functions for tracing values to the browser's
-- developer console, in the spirit of "Debug.Trace" from @base@. Where
-- "Debug.Trace" writes to @stderr@, these functions write to the browser
-- console using
-- <https://developer.mozilla.org/en-US/docs/Web/API/console/log_static console.log>,
-- <https://developer.mozilla.org/en-US/docs/Web/API/console/warn_static console.warn>
-- and
-- <https://developer.mozilla.org/en-US/docs/Web/API/console/error_static console.error>,
-- gaining the browser's affordances such as severity filtering and stack
-- traces.
--
-- The motivation is debugging /pure/ code: places where 'IO' is
-- unavailable or inconvenient, such as a miso application's @view@
-- function or pure helpers called from @update@.
--
-- Like "Debug.Trace", these functions are implemented with
-- 'unsafePerformIO' and are not referentially transparent: they are meant
-- only as a debugging aid and should not be used in production code.
-- Since Haskell is lazily evaluated, a trace fires when (and only when)
-- the traced expression is forced, so messages can appear out of order,
-- once, or not at all.
--
-- = Naming conventions
--
-- The functions follow the naming conventions of "Debug.Trace":
--
-- * @trace*@ functions log with @console.log@, @traceWarn*@ with
--   @console.warn@, and @traceError*@ with @console.error@.
-- * @*Show@ variants accept any 'Show'-able value instead of a string.
-- * @*Id@ variants return the traced value itself.
-- * @*With@ variants trace the result of applying a function to the value.
-- * @*M@ variants trace inside an 'Applicative' (e.g. miso's
--   'Miso.Effect.Effect' monad, or 'IO').
--
-- = See also
--
-- * "Debug.Trace" — the @base@ equivalent, on which this API is modeled
-- * "Miso.FFI" — 'consoleLog', 'consoleWarn', 'consoleError'
----------------------------------------------------------------------------
module Miso.Trace
  ( -- ** Logging (@console.log@)
    trace
  , traceId
  , traceWith
  , traceShow
  , traceShowId
  , traceShowWith
  , traceM
  , traceShowM
    -- ** Errors (@console.error@)
  , traceError
  , traceErrorId
  , traceErrorWith
  , traceErrorShow
  , traceErrorShowId
  , traceErrorShowWith
  , traceErrorM
  , traceErrorShowM
    -- ** Warnings (@console.warn@)
  , traceWarn
  , traceWarnId
  , traceWarnWith
  , traceWarnShow
  , traceWarnShowId
  , traceWarnShowWith
  , traceWarnM
  , traceWarnShowM
    -- ** Generalized tracing
  , traceTo
  ) where
-----------------------------------------------------------------------------
import           System.IO.Unsafe (unsafePerformIO)
import           Prelude
-----------------------------------------------------------------------------
import           Miso.FFI
import           Miso.String
-----------------------------------------------------------------------------
-- | Outputs a message to the browser console with @console.log@ when the
-- result is forced, then returns the second argument. The browser
-- analogue of 'Debug.Trace.trace'.
trace
  :: ToMisoString s
  => s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
trace = traceTo consoleLog
-----------------------------------------------------------------------------
-- | Like 'trace', but returns the message itself:
-- @'traceId' x = 'trace' x x@.
traceId :: ToMisoString s => s -> s
traceId = traceWith id
-----------------------------------------------------------------------------
-- | Traces the result of applying a function to a value, then returns the
-- original value. Useful for logging a projection of a larger structure
-- while leaving the structure untouched.
traceWith
  :: ToMisoString s
  => (a -> s)
  -- ^ Function producing the message from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceWith f a = trace (f a) a
-----------------------------------------------------------------------------
-- | Like 'trace', but accepts any 'Show'-able value as the message. The
-- browser analogue of 'Debug.Trace.traceShow'.
traceShow
  :: Show a
  => a
  -- ^ Value to log
  -> b
  -- ^ Value to return
  -> b
traceShow = trace . show
-----------------------------------------------------------------------------
-- | Shows and traces a value, then returns it. Convenient to wrap around
-- any sub-expression you want to inspect without restructuring the code.
traceShowId :: Show a => a -> a
traceShowId = traceWith show
-----------------------------------------------------------------------------
-- | Traces the 'show'-n result of applying a function to a value, then
-- returns the original value.
traceShowWith
  :: Show b
  => (a -> b)
  -- ^ Function producing the value to show from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceShowWith f = traceWith (show . f)
-----------------------------------------------------------------------------
-- | Traces a message in an 'Applicative' context, such as miso's
-- 'Miso.Effect.Effect' monad or 'IO'. The browser analogue of
-- 'Debug.Trace.traceM'.
traceM :: (ToMisoString s, Applicative f) => s -> f ()
traceM s = trace s $ pure ()
-----------------------------------------------------------------------------
-- | Like 'traceM', but accepts any 'Show'-able value. Useful for logging
-- every action that flows through an update function.
traceShowM :: (Show a, Applicative f) => a -> f ()
traceShowM = traceM . show
-----------------------------------------------------------------------------
-- | Like 'trace', but logs with @console.error@, which browsers render
-- prominently (typically in red, with an expandable stack trace).
traceError
  :: ToMisoString s
  => s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
traceError = traceTo consoleError
-----------------------------------------------------------------------------
-- | Like 'traceId', but logs with @console.error@.
traceErrorId :: ToMisoString s => s -> s
traceErrorId = traceErrorWith id
-----------------------------------------------------------------------------
-- | Like 'traceWith', but logs with @console.error@.
traceErrorWith
  :: ToMisoString s
  => (a -> s)
  -- ^ Function producing the message from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceErrorWith f a = traceError (f a) a
-----------------------------------------------------------------------------
-- | Like 'traceShow', but logs with @console.error@.
traceErrorShow
  :: Show a
  => a
  -- ^ Value to log
  -> b
  -- ^ Value to return
  -> b
traceErrorShow = traceError . show
-----------------------------------------------------------------------------
-- | Like 'traceShowId', but logs with @console.error@.
traceErrorShowId :: Show a => a -> a
traceErrorShowId = traceErrorWith show
-----------------------------------------------------------------------------
-- | Like 'traceShowWith', but logs with @console.error@.
traceErrorShowWith
  :: Show b
  => (a -> b)
  -- ^ Function producing the value to show from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceErrorShowWith f = traceErrorWith (show . f)
-----------------------------------------------------------------------------
-- | Like 'traceM', but logs with @console.error@.
traceErrorM :: (ToMisoString s, Applicative f) => s -> f ()
traceErrorM s = traceError s $ pure ()
-----------------------------------------------------------------------------
-- | Like 'traceShowM', but logs with @console.error@.
traceErrorShowM :: (Show a, Applicative f) => a -> f ()
traceErrorShowM = traceErrorM . show
-----------------------------------------------------------------------------
-- | Like 'trace', but logs with @console.warn@, which browsers render as
-- a warning (typically in yellow) and can be filtered by severity.
traceWarn
  :: ToMisoString s
  => s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
traceWarn = traceTo consoleWarn
-----------------------------------------------------------------------------
-- | Like 'traceId', but logs with @console.warn@.
traceWarnId :: ToMisoString s => s -> s
traceWarnId = traceWarnWith id
-----------------------------------------------------------------------------
-- | Like 'traceWith', but logs with @console.warn@.
traceWarnWith
  :: ToMisoString s
  => (a -> s)
  -- ^ Function producing the message from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceWarnWith f a = traceWarn (f a) a
-----------------------------------------------------------------------------
-- | Like 'traceShow', but logs with @console.warn@.
traceWarnShow
  :: Show a
  => a
  -- ^ Value to log
  -> b
  -- ^ Value to return
  -> b
traceWarnShow = traceWarn . show
-----------------------------------------------------------------------------
-- | Like 'traceShowId', but logs with @console.warn@.
traceWarnShowId :: Show a => a -> a
traceWarnShowId = traceWarnWith show
-----------------------------------------------------------------------------
-- | Like 'traceShowWith', but logs with @console.warn@.
traceWarnShowWith
  :: Show b
  => (a -> b)
  -- ^ Function producing the value to show from the value
  -> a
  -- ^ Value to trace and return
  -> a
traceWarnShowWith f = traceWarnWith (show . f)
-----------------------------------------------------------------------------
-- | Like 'traceM', but logs with @console.warn@.
traceWarnM :: (ToMisoString s, Applicative f) => s -> f ()
traceWarnM s = traceWarn s $ pure ()
-----------------------------------------------------------------------------
-- | Like 'traceShowM', but logs with @console.warn@.
traceWarnShowM :: (Show a, Applicative f) => a -> f ()
traceWarnShowM = traceWarnM . show
-----------------------------------------------------------------------------
-- | The generalized tracing combinator underlying this module: traces via
-- the given console function from "Miso.FFI". Every other function here
-- is defined in terms of it.
traceTo
  :: ToMisoString s
  => (MisoString -> IO ())
  -- ^ Console function to log with, e.g. 'consoleLog'
  -> s
  -- ^ Message to log
  -> a
  -- ^ Value to return
  -> a
{-# NOINLINE traceTo #-}
traceTo f s a = unsafePerformIO $ do
  f (toMisoString s)
  pure a
-----------------------------------------------------------------------------