Defining and Using a Custom Logging Effect in PureScript with Effect Rows
Learn how to define a custom logging effect in PureScript, integrate it with the Eff monad, and compose it with other effects using effect‑row polymorphism.
23 Sept 2026, 15:41 UTC

Desired outcome
Create a reusable logging effect that can be combined with other effects (e.g., Console, Random) using PureScript's effect‑row system, then write a small program that logs messages while performing another side‑effect.
Prerequisites
- PureScript compiler (
purs) version 0.15+ installed.
- Spago (or Pulp) for project management.
- Basic familiarity with the
Eff monad and effect rows.
- A text editor or IDE.
purs) version 0.15+ installed.Eff monad and effect rows.Focused procedure
- Initialize a project
spago init -n logging-effect-demo cd logging-effect-demo spago install purescript-eff purescript-console purescript-randomRun this in a terminal; you need write permission to the directory.
- Define the logging effect
Create a new source file
src/LoggingEffect.purs:module LoggingEffect where import Eff (Eff) import Eff.Exception (EXCEPTION) -- | The effect row element that represents a logging capability. newtype LOG = LOG Unit -- | Run a computation that may log, returning a plain Eff computation. -- The effect is removed from the row after execution. runLog :: forall eff a. Eff (log :: LOG | eff) a -> Eff eff a runLog = unsafeCoerceEff -- implementation detail; see note below -- | Log a string message. log :: forall eff. String -> Eff (log :: LOG | eff) Unit log msg = unsafeCoerceEff (print msg) -- placeholder; actual impl uses console foreign import data LOG :: ! foreign import unsafeCoerceEff :: forall eff a. Eff eff a -> Eff eff a foreign import print :: forall eff. String -> Eff (console :: CONSOLE | eff) UnitExplanation:
LOGis a phantom type that tags the effect row. TherunLogfunction removes theLOGeffect after the computation finishes, usingunsafeCoerceEffas a stand‑in for the real interpreter (in practice you would provide a handler that writes to a logger). - Create a logger interpreter
Add
src/Main.pursthat provides a concrete handler usingConsole:module Main where import Prelude import Eff (Eff) import Eff.Console (CONSOLE, logShow) import Eff.Exception (EXCEPTION) import LoggingEffect (LOG, log, runLog) import Random (Random) -- | A simple program that generates a random number and logs it. program :: forall eff. Eff ( random :: RANDOM , console :: CONSOLE , log :: LOG | eff ) Unit program = do n <- Random.uniform 1 100 log $ "Generated number: " <> show n logShow n -- using Console directly for comparison -- | Remove the LOG effect, leaving only Random and Console. main :: Eff ( random :: RANDOM , console :: CONSOLE | eff ) Unit main = runLog programHere
runLogstrips theLOGrow, somainonly requiresRANDOMandCONSOLE. The type signature shows row polymorphism: the ellipsis| effmeans "any other effects may be present". - Build and run
spago build spago run -x Main.mainExpected behavior: the program prints two lines per execution – one from the custom logger (via the
loghelper) and one from the directConsole.logShowcall. No type‑checking errors should appear.
Expected checks
- Compilation succeeds with no unresolved row variables.
- The output contains both the custom‑log line and the console line.
- Changing the order of effects in the type signature (e.g., putting
LOGafterRANDOM) still compiles, demonstrating row polymorphism.
Recovery options
- If the compiler reports "Could not match type" involving effect rows, add an explicit type annotation to the intermediate function or use
forgetfrompurescript-effto drop unwanted effects. - When mixing effects from different libraries, ensure each effect is imported as a distinct row label; otherwise use
Effect.union-style helpers to merge rows. - For ambiguous errors caused by missing quantification, enable the
-XPartialflag sparingly or add aforall eff.quantifier to make the effect row explicit.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.