Error handling 当有限范围变量的值超出范围时,是否可以覆盖Common Lisp中的类型错误行为?

Error handling 当有限范围变量的值超出范围时,是否可以覆盖Common Lisp中的类型错误行为?,error-handling,integer,range,common-lisp,Error Handling,Integer,Range,Common Lisp,抱歉,如果问题的主题措辞奇怪(因为缺乏更好的术语——这也是我在谷歌上找不到任何东西搜索这个特定主题的原因之一),下面是我的示例 假设此函数foobar已定义为: (defun foobar (x) (declare (type (integer -100 100) x)) (format T "X is ~A~%" x)) 因此,对于declare行,x是一个整数,必须是-100、100或介于两者之间的任何整数。因此,这样做会产生一个错误: CL-USER> (foobar 10

抱歉,如果问题的主题措辞奇怪(因为缺乏更好的术语——这也是我在谷歌上找不到任何东西搜索这个特定主题的原因之一),下面是我的示例

假设此函数foobar已定义为:

(defun foobar (x)
  (declare (type (integer -100 100) x))
  (format T "X is ~A~%" x))
因此,对于declare行,x是一个整数,必须是-100、100或介于两者之间的任何整数。因此,这样做会产生一个错误:

CL-USER> (foobar 101)

The value 101 is not of type (INTEGER -100 100).
   [Condition of type TYPE-ERROR]

   Restarts:
   (blah blah blah)
除了将函数本身更改为显式执行钳制外,是否有一种方法可以指定重写行为,以便在不更改foobar本身的defun的情况下执行此操作:

(foobar [any-value-over-100])
将其钳制为100,同样地,x<-100,而函数体本身没有额外的代码行

编辑:回答一个响应者,这是钳制——将值严格保持在定义的最小和最大范围内。在Lisp中,这是一个示例:

CL-USER> (defun clamp (x min max)
    (if (> x max)
        max
        (if (< x min)
            min
            x)))
CLAMP
CL-USER> (clamp 5 4 9)
5
CL-USER> (clamp -2 4 9)
4
CL-USER> (clamp 123 4 9)
9
<代码>CL-USER>(拆卸夹具(x最小最大值) (如果(>x最大值) 最大值 (如果((夹钳5 4 9) 5. CL-USER>(夹具-2 4 9) 4. CL-USER>(夹子123 4 9) 9 虽然我可以轻松地将其设置为宏并将其放在任何函数的开头(我有一种奇怪的感觉,这最终将是我必须要做的事情),但这个问题是,是否可以告诉常见的Lisp错误处理程序“只使用值执行此操作!”,而不是像通常那样中断整个程序流程。

忽略声明 如果使用编译函数,则类型声明将被忽略

重定义函数 或者,您可以像这样重新定义函数:

(defparameter *foobar-orig* (fdefinition *foobar*))
(defun foobar (x)
  (funcall *foobar-orig* (whatever-you-want x)))
(defun ensure-range (x low high)
  (cond ((< x low) low)
        ((> x high) high)
        (t x)))

(defun foobar (x)
  (let (x (ensure-range x -100 100))
    (format T "X is ~A~%" x)))
使用重新启动 您最好的方法是用声明替换声明并建立适当的声明,例如


该标准不提供全局错误处理程序,而是提供实现。

如果我理解正确,您希望确保整数在该范围内,如果是这种情况,我认为您不应该使用类型错误来处理它,而是(let…)类似于以下内容:

(defparameter *foobar-orig* (fdefinition *foobar*))
(defun foobar (x)
  (funcall *foobar-orig* (whatever-you-want x)))
(defun ensure-range (x low high)
  (cond ((< x low) low)
        ((> x high) high)
        (t x)))

(defun foobar (x)
  (let (x (ensure-range x -100 100))
    (format T "X is ~A~%" x)))
(解除锁定确保范围(x低-高)
(条件(x高)高)
(t x)))
(十)
(让(x(确保范围x-100))
(格式T“X为~A~%”X)

公共Lisp中的类型声明

您的代码:

(defun foobar (x)
  (declare (type (integer -100 100) x))
  (format T "X is ~A~%" x))
上面用
(foobar 120)
之类的东西调用的结果在Common Lisp中完全没有定义

  • 它可能被完全忽略
  • 它可能会导致错误或各种运行时问题
  • 它可以帮助编译器创建更好的代码(顺便说一句,这是这些声明的主要原因)
  • 可以在编译/运行时对其进行类型检查。只有很少的Lisp编译器能做到这一点
公共Lisp中的可移植运行时类型检查

如果要便携式检查运行时类型错误,请使用
check-type
ASSERT

(defun foobar (x)
  (check-type x (integer -100 100))
  (format T "X is ~A~%" x))
建议

在Lisp中,在不更改源代码的情况下扩展函数称为“建议”。这不是普通函数的通用Lisp标准,但应该有相应的工具,编写这样的东西并不难

扩展通用函数

CommonLisp为通用函数内置了这种机制。标准方法组合有:before、:after和:about

(defmethod foobar ((x integer))
  (check-type x (integer -100 100))
  (format T "X is ~A~%" x))
在CommonLisp中,不能对任意类型进行分派—只能对类进行分派。有一些基本类型的类,如字符串、整数等。。。这里我们使用
x
是一个
整数

如果要夹紧
foobar
x

(defmethod foobar :around ((x integer))
  (call-next-method (clamp x -200 100)))
上面是一个
:around
方法。我调用下一个方法,即上面的方法,并更改参数。只要参数类不更改分派,这是允许的

替代方法:宏观

一个目标可能是编写更少的代码,让代码更具声明性

也许有人想写:

(defun-clamped foobar ((x (integer :min -100 :clampled-max 100)))
  (format T "X is ~A~%" x))

然后我只需编写
defun-clipped
宏,它将扩展为一个正常的
defun
,执行必要的操作。

如果可以接受其他内容,请不要声明x介于-100和100之间。我认为,如果违反声明,实现可能允许任何类型的内存损坏。 那么,做什么呢

(declare (optimize (safety 0)))
避免声明抛出错误并不是一个好主意。 您可以先钳制该值,然后将函数定义的其余部分放入LET形式

(defun foo (x)
  (declare (type integer x))
  (let ((x (clamp x -100 100)))
    (declare (type (integer -100 100)  ; LET allows declarations, too!
                   x))
    (bar x)))
如果希望宏为您执行此操作,则应使用以下类似的方法:

(defmacro defclamp (name (arg min max)
                    &body body)
  `(defun ,name (,arg)
     (declare (type real ,arg))
     (let ((,arg (clamp ,arg ,min ,max)))
       (declare (type (real ,min ,max)
                      ,arg))
       ,@body)))

我无法在这个回复中输入正确格式的代码,所以我编辑了我的原始帖子来解释钳制功能。我不希望忽略类型声明,但如果整数超出范围整数类型声明的边界,则它的行为会有所不同,而不是调用错误。我更喜欢(INTEGER[min][max])类型声明,因为它有助于编译器进行某些优化,也有助于在REPL上执行(描述“foobar”)时的整个“自文档化代码”思想,这将忠实地描述该参数的类型声明。我认为我将采用的解决方案是将您的宏建议与sds提到的(declare(optimize(safety 0))结合起来,至少在SBCL中,安全设置为0允许我为具有范围值参数的函数指定该参数的越界值,而不会发生错误,并且在使用descripe函数时仍然显示正确的(整数-100)类型声明。因此,正如你所建议的,我可以编写一个定制的“增强型DeFun”宏,它定义了0个安全设置的函数,然后将自定义的箝位代码插入到函数的序幕中。虽然我看到了“安全性”,但我没有考虑内存损坏。