LISP - 錯誤處理


物件導向的錯誤處理- LISP條件系統

在Common Lisp的術語中,異常被稱為條件。

事實上,條件比在傳統程式設計語言的異常更為普遍,因為一個條件表示任何事件,錯誤與否,這可能會影響各級函式呼叫堆疊。

在LISP狀態處理機制,處理的條件是用來警告信號(例如通過列印一個警告),而在呼叫堆疊的上層程式碼可以繼續工作,這樣的情況下以這樣一種方式。

條件處理系統中LISP有三個部分:

  • 信號的條件

  • 處理條件

  • 重新啟動進程

處理一個條件

讓我們處理由除零所產生的條件的例子,在這裡解釋這些概念。

需要處理的條件如下步驟:

  1. 定義條件 - “條件是一個物件,它的類表示條件的一般性質,其範例資料進行有關的特殊情況,導致被示意條件的細節資訊”。

    定義條件的巨集用於定義一個條件,它具有以下語法:

    (define-condition condition-name (error)
      ((text :initarg :text :reader text)))

    :initargs 引數,新的條件物件與MAKE-CONDITION 巨集,它初始化的基礎上,新的條件下的插槽中建立的。

    在我們的例子中,下面的程式碼定義的條件:

    (define-condition on-division-by-zero (error)
       ((message :initarg :message :reader message)))
    
  2. 編寫處理程式 - 條件處理程式是用於處理信號的條件在其上的程式碼。它一般寫在呼叫該函式出問題的上級功能之一。當條件信號發生時,該信號轉導機制中搜尋基於所述條件的類合適的處理器。

    每個處理程式包括:

    • 型別說明符,它指示條件,它可以處理的型別

    • 一個函式,它接受一個引數條件

    當條件獲得信號,該信號機制發現最近建立的處理程式與條件型別相容,並呼叫它的函式。

    巨集處理程式的情況建立了一個條件處理程式。一個處理程式的 handler-case 形式:

    (handler-case expression
      error-clause*)

    那麼,每個error從句的形式為:

    condition-type ([var]) code)
  3. 重新啟動階段

    這是真正從錯誤的程式碼中恢復程式,條件處理程式可以通過呼叫一個適當的重新啟動處理的條件。重新啟動程式碼一般是放置在中層或底層函式和條件處理程式被放置到應用程式的上層。

    handler-bind巨集允許提供一個重新啟動功能,並允許繼續在較低階的功能,無需解除函式的呼叫堆疊。換句話說,控制流將仍然處於較低水平的功能。

    handler-bind的基本形式如下:

    (handler-bind (binding*) form*)

    其中每個系結如以下列表:

    • 條件型別

    • 一個引數的處理常式

    invoke-restart巨集查詢並呼叫具有指定名稱作為引數最近系結重新啟動功能。

    可以有多個重新啟動。

範例

在這個例子中,我們演示了上述概念通過寫一個名為劃分功能函式,則會建立錯誤條件,如果除數引數為零。我們有三個匿名的功能,提供三種方式來出它 - 通過返回一個值1,通過傳送一個除數2和重新計算,或通過返回1。

建立一個名為main.lisp一個新的原始碼檔案,並在其中輸入如下程式碼:

(define-condition on-division-by-zero (error)
   ((message :initarg :message :reader message)))
   
(defun handle-infinity ()
   (restart-case
       (let ((result 0))
         (setf result (division-function 10 0))
         (format t "Value: ~a~%" result))
     (just-continue () nil)))
     
 (defun division-function (value1 value2)
   (restart-case
       (if (/= value2 0)
           (/ value1 value2)
           (error 'on-division-by-zero :message "denominator is zero"))

     (return-zero () 0)
     (return-value (r) r)
     (recalc-using (d) (division-function value1 d))))

 (defun high-level-code ()
   (handler-bind
       ((on-division-by-zero
         #'(lambda (c)
             (format t "error signaled: ~a~%" (message c))
             (invoke-restart 'return-zero)))
     (handle-infinity))))

   (handler-bind
       ((on-division-by-zero
         #'(lambda (c)
             (format t "error signaled: ~a~%" (message c))
             (invoke-restart 'return-value 1))))
     (handle-infinity))

   (handler-bind
       ((on-division-by-zero
         #'(lambda (c)
             (format t "error signaled: ~a~%" (message c))
             (invoke-restart 'recalc-using 2))))
     (handle-infinity))

   (handler-bind
       ((on-division-by-zero
         #'(lambda (c)
             (format t "error signaled: ~a~%" (message c))
             (invoke-restart 'just-continue))))
     (handle-infinity))

   (format t "Done."))

當執行程式碼,它返回以下結果:

error signaled: denominator is zero
Value: 1
error signaled: denominator is zero
Value: 5
error signaled: denominator is zero
Done.

除了“系統狀態”,如上文所討論,普通的LISP還提供了各種功能,其可被稱為信令錯誤。當信號實現相關處理錯誤。

LISP的錯誤信號功能

下表提供了常用功能的信令警告,休息,非致命和致命的錯誤。

使用者程式指定一個錯誤資訊(字串)。該函式處理這個訊息,並且可能/可能不會顯示給使用者。

錯誤資訊應該通過應用的格式化功能進行構造,不應該在開頭或結尾包含一個換行符,也無需指明錯誤,如LISP系統將根據其喜好的樣式利用這些服務。

SL No. 函式和說明
1

error format-string &rest args

它標誌著一個致命的錯誤。這是不可能從這種錯誤的繼續;這樣的錯誤將永遠不會返回到其呼叫者。

2

cerror continue-format-string error-format-string &rest args

它發出錯誤信號,並進入偵錯器。但是,它允許程式從偵錯器解決錯誤之後繼續。

3

warn format-string &rest args

它列印一條錯誤訊息,但一般不會進入偵錯

4

break &optional format-string &rest args

它列印的訊息,並直接進入偵錯器,而不允許攔截由程式設計錯誤處理設施的任何可能性

範例

在這個例子中,階乘函式計算一個數階乘;但是,如果引數為負,它丟擲一個錯誤條件。

建立一個名為main.lisp一個新的原始碼檔案,並在其中輸入如下程式碼:

(defun factorial (x)
   (cond ((or (not (typep x 'integer)) (minusp x))
          (error "~S is a negative number." x))
         ((zerop x) 1)
         (t (* x (factorial (- x 1))))))
         
(write(factorial 5))
(terpri)
(write(factorial -1))

當執行程式碼,它返回以下結果:

120
*** - -1 is a negative number.