Git Product home page Git Product logo

more-conditions's Introduction

Introduction

The more-conditions system contains some condition classes, functions and macros which may be useful when building slightly complex systems.

One aspect handled by these classes is “chaining” of conditions. Chaining is one way to implement signaling and handling of conditions in a layered architecture: layers handle (not necessarily in the sense of unwinding the stack and transferring control) conditions signaled from lower layers by wrapping them in an appropriate condition instances and re-signaling. Support for this pattern is provided by the following classes, functions and macros:

  • chainable-condition
  • cause and root-cause
  • maybe-print-cause
  • with-condition-translation
  • define-condition-translating-method

See *Condition Translation at Layer Boundaries below.

Another aspect are custom simple conditions. The function maybe-print-explanation is intended to make writing report functions for custom simple conditions easier. See <a href=”*Custom Simple Conditions”>*Custom Simple Conditions below. more-conditions also provides a couple of specific program-error s:

  • program-error
    • missing-required-argument
      • missing-required-initarg
    • incompatible-arguments
      • incompatible-initargs
    • initarg-error
      • missing-required-initarg
      • incompatible-initargs

Furthermore, more-conditions contains conditions for reporting progress of operations up the callstack in a minimally invasive way. See *Tracking and Reporting Progress of Operations.

Finally, more-conditions supports embedding documentation references in conditions via condition-references and the reference-condition mixin. See <a href=”*Embedding Documentation References in Conditions”>*Embedding Documentation References in Conditions below.

https://travis-ci.org/scymtym/more-conditions.svg

Error Behaviors for Layered Systems

Condition Translation at Layer Boundaries

Assume some complex backend whose primary entry point is a emit (NODE OPERATION TARGET) function. Part of the backend interface is an emit-error condition. In such a situation, it is desirable to translate arbitrary conditions that may arise anywhere in the backend into the emit-error condition without loosing information regarding the root cause of the problem.

Using more-conditions, the problem can be solved as follows:

(defgeneric emit (node operation target))

(define-condition emit-error (cl:error
                              more-conditions:chainable-condition)
  ((node      :initarg :node
              :reader  node)
   (operation :initarg :operation
              :reader  operation)
   (target    :initarg :target
              :reader  target))
  (:report (lambda (condition stream)
             (format stream "Could not ~S node ~A to target ~A~/more-conditions:maybe-print-cause/"
                     (operation condition)
                     (node condition)
                     (target condition)
                     condition))))

(more-conditions:define-condition-translating-method emit (node operation target)
  ((error emit-error)
   :node node
   :operation operation
   :target target))

(defmethod emit ((node real) (operation (eql :foo)) (target stream))
  (when (minusp node)
    (error "Cannot ~S for negative real node ~A."
           operation node)))

This does the following:

  • Translate any error condition into an emit-error condition
  • Store the node, operation, target and causing condition in the emit-error condition (can be retrieved via more-conditions:cause or more-conditions:root-cause)
  • Do not unwind the stack since translation is done in handler-bind rather than handler-case. This way the client can still determine the location of the causing condition.
  • Avoid wrapping multiple emit-error s around each other in case of recursive emit calls.

Now (emit -1 :foo *standard-output*) signals an emit-error which describes the failed operation and contains a detailed description of the actual problem as its more-conditions:cause:

Could not :FOO node -1 to target #<SLIME-OUTPUT-STREAM {1005DAF453}> Caused by:
> Cannot :FOO for negative real node -1.

Error Policies at Layer Boundaries

When working with protocol functions, say find-thing for example, different behaviors in case of errors may be desirable under different circumstances such as

  • Return nil or some other value in case of errors in order to being able to write (or (find-thing ARGS) SOMETHING-ELSE)
    • For efficiency reasons, it made be desirable to avoid establishing handlers and/or restarts in this case
  • Return nil or some other value and signal a style-warning when a compile-time check fails or an optimization opportunity is missed
  • Signal an error when the computation cannot continue without the result
    • Establish restarts for higher layers to decide about error recovery

more-conditions provides the error-behavior-restart-case macro for such situations. It can be used as demonstrated in the following example:

(define-condition not-found-condition ()
  ((name :initarg :name)))

(define-condition not-found-warning (warning not-found-condition)
  ())

(define-condition not-found-error (error not-found-condition)
  ())

(defmethod find-thing ((name t) &key)
  nil)

(defmethod find-thing :around ((name t)
                               &key (if-does-not-exist #'error))
  (or (call-next-method)
      (more-conditions:error-behavior-restart-case
       (if-does-not-exist (not-found-error :name name)
                          :warning-condition   not-found-warning
                          :allow-other-values? t)
       (retry ()
         (find-thing name))
       (use-value (value)
         value))))

Now, calling find-thing with different error policies results in different behaviors:

(find-thing :foo)
|- ERROR: Condition NOT-FOUND-ERROR was signalled

(find-thing :foo :if-does-not-exist #'warn)
| WARNING: Condition NOT-FOUND-WARNING was signalled
=> nil

(find-thing :foo :if-does-not-exist nil)
=> nil

(handler-bind ((error (lambda (c)
                        (declare (ignore c))
                        (invoke-restart 'use-value :value))))
  (find-thing :foo))
=> :value

Custom Simple Conditions

A custom simple conditions can be defined as follows:

(define-condition simple-frob-error (cl:error
                                     cl:simple-condition)
  ((foo :initarg :foo
        :reader  foo))
  (:report (lambda (condition stream)
             (format stream "Could not frob ~S~/more-conditions:maybe-print-explanation/"
                     (foo condition)
                     condition))))

(defun simple-frob-error (foo &optional format &rest args)
  (error 'simple-frob-error
         :foo              foo
         :format-control   format
         :format-arguments args))

Now (simple-frob-error :bar) and (simple-frob-error :bar "Fez ~S." :whoop) both produce nice reports.

Tracking and Reporting Progress of Operations

Despite the most frequently used condition superclasses, cl:error and cl:warning, the Common Lisp condition system allows arbitrary other subclasses of cl:condition which are not a-priori associated with certain control transfer behavior. The more-conditions system exploits this for providing a family of conditions which indicate progress of operations without necessarily affecting flow of control or program execution in general.

The more-conditions system does not address the question of handling progress conditions (But see =user-interface.progress=). This is intended to allow “speculative” signaling of progress conditions from as many operations as possible without introducing dependencies beyond more-conditions into the signaling system. Further, signaling code does not have to care or even know whether the signaled progress conditions are actually handled or not in a particular situation since program execution remains unaffected. Despite the hopefully low impact on program design and code organization, there is some overhead involved in signaling, and potentially handling, progress conditions. Therefore, some amount of care is required when signaling progress conditions form inner loops.

In the more-conditions system, there are two builtin progress condition classes: more-conditions:progress-condition and more-conditions:simple-progress-condition. Support for signaling these conditions is provided in form of the function more-conditions:progress and the macros more-conditions:with-trivial-progress and more-conditions:with-sequence-progress.

These can be used as follows (the outer cl:handler-bind is required for the signaled progress conditions to produce an observable effect):

(handler-bind ((more-conditions:progress-condition #'princ))
  (more-conditions:progress :my-operation 0 "Preparing")
  (sleep 1)
  (more-conditions:progress :my-operation 1/3 "Processing ~A" :data)
  (sleep 1)
  (more-conditions:progress :my-operation 2/3 "Cleaning up")
  (sleep 1)
  (more-conditions:progress :my-operation t))

The more-conditions:with-trivial-progress macro can be used to indicate execution of long running operations without reporting detailed progress during execution:

(handler-bind ((more-conditions:progress-condition #'princ))
  (more-conditions:with-trivial-progress (:factorial "Computing factorial of ~D" 1000)
    (alexandria:factorial 1000)))

For the common case of processing data sequentially, the more-conditions:with-sequence-progress macro can be used to easily signal progress conditions:

(handler-bind ((more-conditions:progress-condition #'princ))
  (let ((items (alexandria:iota 5)))
    (more-conditions:with-sequence-progress (:frob items)
      (dolist (item items)
        (more-conditions:progress "Processing element ~A" item)
        (sleep 1)))))

When using higher-order functions to process sequences the more-conditions:progressing function can be used:

(handler-bind ((more-conditions:progress-condition #'princ))
  (let ((items (alexandria:iota 5)))
    (more-conditions:with-sequence-progress (:frob items)
      (mapcar (more-conditions:progressing #'1+ :frob) items))))

Embedding Documentation References in Conditions

It is sometimes useful to include pointers to documentation in signaled conditions. more-conditions supports this via the generic function condition-references and the mixin class reference-condition. condition-references returns a list of references of the form (DOCUMENT PART [LINK]). The type reference-spec and the readers reference-document, reference-part, reference-link deal with these references. reference-condition stores a list of such references and condition-references collects all references traversing cause relations.

For example, the following condition

(define-condition foo-error (error
                             more-conditions:reference-condition
                             more-conditions:chainable-condition)
  ()
  (:report (lambda (condition stream)
             ;; Prevent reference printing in causing condition(s)
             (let ((more-conditions:*print-references* nil))
               (format stream "Foo Error.~/more-conditions:maybe-print-cause/"
                       condition)))))

(error 'foo-error
       :cause      (make-condition 'foo-error
                                   :references '((:foo "bar")
                                                 (:foo "baz")
                                                 (:bar "fez" "http://whoop.org")))
       :references '((:foo "bar")
                     (:fez "whiz")))

would print the following report:

Foo Error. Caused by:
> Foo Error.
See also:
  FOO, bar
  FOO, baz
  BAR, fez <http://whoop.org>
  FEZ, whiz

Note how references from the causing condition are collected and printed.

settings

more-conditions's People

Contributors

scymtym avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

salewski

more-conditions's Issues

Can not be loaded on lisps with older ASDF, due to "Package UIOP does not exist"

On many lisps, including even the recent CCL 1.9, more-conditions can't be loaded due to (uiop:symbol-call '#:more-conditions.test "RUN-TESTS") in more-conditions.asd, because UIOP package is included only into very recent ASDF.

CCL 1.9 logs:
http://cl-test-grid.appspot.com/blob?key=1pefbsgzz0
Results from all lisps:
http://common-lisp.net/project/cl-test-grid/library/more-conditions.html

But UIOP may be loaded as a separate library even with old ASDF, so the problem may be fixed by adding :defsystem-depends-on (:uiop) to the :more-conditions-test system definition, or just using (funcall (read-from-string "more-conditions.test:run-tests")) instead of uiop:symbold-call.

CLOSER-MOP does not match version 0.61

Hello.

You recently specified :depends-on ((:version :closer-mop "0.61"))
in the more-conditions.asd

But the latest closer-mop version, coming with Quicklisp, is 1.0.0.
asdf:version-satisfies-p in some versions of ASDF considers
0.61 and 1.0.0 to be incompatible.

In result more-conditions and the systems using it fail to load
on Lisps where such ASDF is used:
http://common-lisp.net/project/cl-test-grid/ql/qlalpha-2014-04-21-diff2.html.

I would recomend to just specify :depends-on (:closer-mop), leaving
the choice of correct version to external configuration (Quicklisp).

FYI, this problem was dicussed some time ago in asdf-devel,
when moptilities failed because specified :depends-on ((:version :closer-mop "0.55")) :
https://www.mail-archive.com/[email protected]/msg03384.html

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.