On this page:
10.1 Fundamentals
messy-log/  c
logged
logged/  c
10.2 Terminal Values
SUCCESS
FAILURE
10.3 Alternative Constructors
logged-unit
logged-failure
logged-attachment
logged-map
coerce-logged
logged-acyclic
$cycle
10.4 Logged Program Control
define-logged
dump-log
10.5 Entry Points for Logged Programs
run-log
get-log
10.6 Testing Logged Procedures
test-logged-procedure
8.0

10 Logged Procedures

 (require xiden/logged) package: xiden

A logged procedure is an instance of the monadic value type logged. An instance of logged contains a normal Racket procedure that returns some value along with some messages representing a program log. A program composed of logged procedures eventually terminates with a complete message log along with a SUCCESS or FAILURE value.

10.1 Fundamentals

A messy log is a message or an arbitrarily-nested list where messages are the only non-list elements.

This contract is not used in some parts of the implementation for performance reasons, but will be cited in this reference for clarification reasons.

struct

(struct logged (thnk)
    #:transparent)
  thnk : (-> (listof $message?) (values any/c (listof messy-log/c)))
A monadic type that represents a computed value alongside messages. The thnk must accept a list of currently accumulated messages, then perform planned work that may add at least zero new messages.

The latest message must come first in the resulting list, so cons is appropriate for adding a new message.

(logged (lambda (messages)
  (values (+ 2 2)
          (cons ($show-string "Putting 2 and 2 together")
                messages))))

If a computation must halt, use SUCCESS or FAILURE as the computed value.

(logged (lambda (messages)
  (values FAILURE
          (cons ($show-string "I can't go on!")
                messages))))

It’s fine to cons another list of messages onto an existing list. This creates a messy log.

(logged (lambda (messages)
  (values whatever
          (cons (list ($show-string ...) ($show-datum ...))
                messages))))

syntax

(logged/c contract-expr)

Produces a contract for a logged procedure. The procedure must return a value matching contract-expr as the first value, unless that value is SUCCESS or FAILURE.

10.2 Terminal Values

value

SUCCESS : symbol?

value

FAILURE : symbol?

When using an instance of logged, evaluation stops at the moment one of these uninterned symbols are encountered.

Xiden uses these values to distinguish between Racket booleans and values that control logged evaluation.

10.3 Alternative Constructors

procedure

(logged-unit v)  logged?

  v : any/c
Returns a logged instance that yields v as a computed value, with no added messages.

procedure

(logged-failure variant)  logged?

  variant : any/c
Returns (logged (λ (m) (values FAILURE (cons V messages)))), where V is

procedure

(logged-attachment v next)  logged?

  v : any/c
  next : (or/c $message? (listof $message?))
Returns (logged (λ (m) (values v (cons next m)))).

procedure

(logged-map f to-map)  logged?

  f : (-> $message? $message?)
  to-map : logged?
Returns a new logged instance such that each message produced by to-map is included in the combined log using (map f (run-log to-map null)).

Use this to “scope” messages.

(define-message $build-log-entry (name message))
 
; hypothetical
(define (create-build) (logged (lambda (messages) ...)))
 
(define build
  (logged-map (curry $build-log-entry "my-build")
              (create-build)))

procedure

(coerce-logged v)  logged?

  v : any/c
Equivalent to (if (logged? v) v (logged-unit v))

procedure

(logged-acyclic key proc)  logged?

  key : any/c
  proc : (-> (listof $message?) (values any/c (listof messy-log/c)))

struct

(struct $cycle $message (key))

  key : any/c
logged-acyclic behaves like (logged proc) with cycle detection. If another logged instance runs in the context of proc, and that instance was constructed using logged-acyclic and a value equal? to key, then evaluation ends early. In that case, the computed value is FAILURE and ($cycle key) appears in the log.

10.4 Logged Program Control

syntax

(define-logged (id formals ...) body ...)

Like (define (id formals ...) body ...), except the procedure runs in a continuation with the following injected procedure bindings:

The following example defines two equivalent procedures that clarify how define-logged reduces code volume.

(define-logged (interpret variant)
  (cond [(eq? 'ok variant)
         ($pass ($show-string "Result is okay"))]
        [(eq? 'no variant)
         ($fail ($show-string "Result is not okay"))]
        [(logged? variant)
         (call-with-values ($run! variant) $use)]))
 
(define (interpret result)
  (logged
   (lambda (messages)
     (call/cc
       (lambda (return)
         (cond [(eq? 'ok variant)
                (return SUCCESS ($show-string "Result is okay"))]
               [(eq? 'no variant)
                (return FAILURE ($show-string "Result is not okay"))]
               [(logged? variant)
                (call-with-values (run-log variant messages) return)]))))))

procedure

(dump-log [#:dump-message dump-message    
  #:force-value value]    
  preamble ...)  (logged/c any/c)
  dump-message : (-> $message? any) = writeln
  value : any/c = (void)
  preamble : $message?
Returns a logged procedure that applies dump-message to every element of the preamble, then every element in the current program log. The logged procedure will use value as the result.

10.5 Entry Points for Logged Programs

procedure

(run-log program [messages])  
any/c (listof $message?)
  program : logged?
  messages : (listof $message?) = null
Applies all delayed work in program. Returns a value and a list of messages representing log output.

procedure

(get-log program)  (listof $message?)

  program : logged?
Like run-log, but returns only the list of messages attached to the computed value.

Additionally, that list is flattened, then reversed.

10.6 Testing Logged Procedures

 (require (submod xiden/logged test))

procedure

(test-logged-procedure [#:with initial]    
  test-message    
  logged-procedure    
  continue)  void?
  initial : (listof $message?) = null
  test-message : string?
  logged-procedure : logged?
  continue : procedure?
Equivalent to a unit test case with the given test-message, where the test evaluates

(call-with-values (λ () (run-log logged-procedure initial)) continue)

.