LISP - 错误处理

  • 简述

    在 Common LISP 术语中,异常称为条件。
    事实上,条件比传统编程语言中的异常更通用,因为condition表示任何可能会影响函数调用堆栈不同级别的事件、错误或不发生。
    LISP 中的条件处理机制以这样的方式处理此类情况:条件用于发出警告(例如通过打印警告),而调用堆栈上的上层代码可以继续其工作。
    LISP 中的条件处理系统分为三个部分 -
    • 发出条件信号
    • 处理条件
    • 重启进程
  • 处理条件

    让我们举一个处理由被零除条件引起的条件的例子来解释这里的概念。
    您需要采取以下步骤来处理条件 -
    • Define the Condition- “条件是一个对象,其类指示条件的一般性质,其实例数据携带有关导致发出信号的特定情况的详细信息的信息”。
      define-condition 宏用于定义条件,语法如下:
    
    (define-condition condition-name (error)
       ((text :initarg :text :reader text))
    )
    
    • 使用 MAKE-CONDITION 宏创建新的条件对象,它根据:initargs争论。
    在我们的示例中,以下代码定义了条件 -
    
    (define-condition on-division-by-zero (error)
       ((message :initarg :message :reader message))
    )
    
    • Writing the Handlers− 条件处理程序是用于处理在其上发出信号的条件的代码。它通常写在调用错误函数的高级函数之一中。当发出条件信号时,信号机制会根据条件的类搜索合适的处理程序。
      每个处理程序包括 -
      • 类型说明符,指示它可以处理的条件类型
      • 接受单个参数的函数,即条件
      当发出条件信号时,信号机制会找到最近建立的与条件类型兼容的处理程序并调用其函数。
      宏观handler-case建立一个条件处理器。处理程序案例的基本形式 -
    
    (handler-case expression error-clause*)
    
    其中,每个错误条款的形式为 -
    
    condition-type ([var]) code)
    
    • Restarting Phase
      这是实际从错误中恢复程序的代码,然后条件处理程序可以通过调用适当的重新启动来处理条件。重启代码通常放在中层或低层函数中,条件处理程序放在应用程序的上层。
      handler-bind宏允许您提供重新启动功能,并允许您在不展开函数调用堆栈的情况下继续执行较低级别的函数。换句话说,控制流仍将处于较低级别的功能中。
      的基本形式handler-bind如下 -
    
    (handler-bind (binding*) form*)
    
    每个绑定都是以下列表 -
    • 条件类型
    • 一个参数的处理函数
    invoke-restart宏以指定名称作为参数查找并调用最近绑定的重启函数。
    您可以多次重新启动。

    例子

    在这个例子中,我们通过编写一个名为 division-function 的函数来演示上述概念,如果除数参数为零,它将创建一个错误条件。我们有三个匿名函数,它们提供了三种方法 - 返回值 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.
    
    除了上面讨论的“条件系统”之外,Common LISP 还提供了各种函数,可以调用这些函数来发出错误信号。然而,当发出错误信号时,错误的处理是依赖于实现的。
  • LISP 中的错误信号函数

    下表提供了发出警告、中断、非致命和致命错误信号的常用函数。
    用户程序指定错误消息(字符串)。这些功能处理此消息,可能会/可能不会将其显示给用户。
    错误消息应该通过应用构造format函数,开头或结尾不应包含换行符,也不需要指示错误,因为 LISP 系统会根据其首选样式处理这些问题。
    序号 功能及说明
    1
    error 格式字符串&rest args
    它表示一个致命错误。不可能从这种错误继续下去;因此错误永远不会返回给它的调用者。
    2
    cerror 继续格式字符串错误格式字符串&rest args
    它发出错误信号并进入调试器。但是,它允许在解决错误后从调试器继续执行程序。
    3
    warn 格式字符串&rest args
    它打印一条错误消息,但通常不会进入调试器
    4
    break&optional格式字符串&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.