Writer
Writer[W] lets you accumulate values of type W during a computation. This is useful for collecting events, audit logs, diagnostics, or any output that builds up as your logic runs.
Basic usage
import purelogic.*
def process(using Writer[String]): Int = {
write("Starting")
write("Processing")
42
}
val (logs, result) = Writer {
process
}
// logs: Vector("Starting", "Processing")
// result: 42Functions
write
Appends a single value to the log:
write("Order validated")writeAll
Appends multiple values at once:
writeAll(List("Step 1", "Step 2", "Step 3"))clear
Clears all accumulated values:
write("this will be gone")
clear
write("fresh start")
// only "fresh start" will be in the logcapture
Runs a block in a nested scope, returning both the captured writes and the result. The captured writes are also forwarded to the outer writer:
def process(using Writer[String]): Unit = {
write("before")
val (captured, result) = capture {
write("inside")
42
}
write("after")
// captured: Vector("inside")
// outer log: Vector("before", "inside", "after")
}snapshot / rollback
snapshot captures the current contents of the log, and rollback restores it to a captured snapshot, discarding any writes made since. Values accumulated before the snapshot are preserved, so this is a targeted reset rather than a full clear.
def process(using Writer[String]): Unit = {
write("before")
val snap = snapshot
write("inside")
rollback(snap)
// log: Vector("before")
}These are the low-level primitives that recover builds on. Reach for them directly when you need custom recovery semantics that the built-in recover family does not cover. A common example is a lookahead that should leave no trace on success but keep its writes when a committed failure escapes:
def isSuccessful(block: Abort[String] ?=> Unit)(using Writer[String], State[Unit], Abort[String]): Boolean = {
val snap = snapshot
val result = recoverSome {
block
true
} { case "recoverable" => false }
rollback(snap) // only reached on success or a handled failure
result
}If the block succeeds or fails recoverably, its writes are rolled back. If it fails with a committed error, the failure escapes before rollback runs, so those writes stay in the log for the outer scope to observe.
Running
Writer(body) returns a tuple of the accumulated values and the result:
val (logs: Vector[W], result: A) = Writer {
myProgram
}