3

在仅 IPv6 的网络中工作时,我尝试用 Clozure CL 替换 SBCL,但遇到了这样的错误:

MIGRATIONS> (ignore-errors (ccl:make-socket :remote-host "ya.ru" :remote-port 443))
NIL
#<CCL:NO-APPLICABLE-METHOD-EXISTS #x302005215E5D>
MIGRATIONS> (ignore-errors (ccl:make-socket :remote-host "ya.ru" :remote-port 443 :address-family :internet))
NIL
#<CCL:NO-APPLICABLE-METHOD-EXISTS #x3020052549AD>
MIGRATIONS> (ignore-errors (ccl:make-socket :remote-host "ya.ru" :remote-port 443 :address-family :internet6))
#<BASIC-TCP-STREAM ISO-8859-1 (SOCKET/16) #x3020051D4A9D>

问题是许多库在使用CCL:MAKE-TCP-SOCKET时没有指定地址族或指定:internet.

有没有办法ccl:make-socket在运行时修补以覆盖此设置?

4

2 回答 2

5

建议功能

Common Lisp 的几个实现允许建议(->修补)正常功能。Advising 是一个非标准功能,不同的实现以稍微不同的方式提供它。具有 :before、:after 和 :around 方法的 CLOS 通用函数的相关机制是标准化的。

目的是在定义函数后,在不改变原始源代码的情况下,向函数添加一个或多个补丁。

通常,这要求对该函数的函数调用不是内联的。

Clozure Common Lisp 中的宏 ADVISE

Clozure CL中的修补功能可以使用宏来完成ADVISE。请参阅文档以获取建议

假设我们有一个函数FOOBAR

? (defun foobar (a b &key c (d :foobar)) (list a b c d))
FOOBAR

FOOBAR在里面被调用TEST

? (defun test (a) (foobar a 20 :c 30))
TEST

? (test 10)
(10 20 30 :FOOBAR)

我们现在想要打补丁,以便用不同的值调用FOOBAR命名的 arg 。:D

我们更改 arglist 以在两个必需的 args 之后插入新的命名参数:

? (advise foobar (let ((arglist (list* (first arglist)
                                       (second arglist)
                                       :d :ipv6
                                       (cddr arglist))))
                   (:do-it))   ; calling the original function
          :when :around     ; advise around it
          :name :ipv6)      ; the name of this advise
#<Compiled-function (CCL::ADVISED 'FOOBAR) (Non-Global)  #x3020010D1CCF>

现在我们可以调用我们的TEST函数,它会调用建议的函数FOOBAR

? (test 10)
(10 20 30 :IPV6)

建议 CCL:MAKE-SOCKET

您可以为CCL:MAKE-SOCKET.

未经测试:

(advise ccl:make-socket (let ((arglist (list* :address-family
                                              :internet6
                                              arglist)))
                          (:do-it))
        :when :around
        :name :internet6)
于 2017-09-08T17:59:22.273 回答
2

这是可以做到的!

首先复制原始的make-socket

(IN-PACKAGE :ccl)
(DEFPARAMETER original-make-socket #'make-socket)

然后重新定义make-socket。注意:您必须提供所有关键字参数的完整规范。实际上,我只使用了您问题中的那些进行演示。

(defun make-socket (&key (remote-host "defau.lt") 
                         (remote-port 443) 
                         (address-family :internet6))
  (declare (ignore address-family))
  (format t "Calling new make-socket with address-family as internet6!")
  (funcall original-make-socket 
           :remote-host remote-host 
           :remote-port remote-port 
           :address-family :internet6))

这将发出一个持续错误的信号。

在 repl 处键入:go以继续。这将成功修补 make-socket。

现在对 make-socket 的任何调用都将指向新定义。尝试:

(IN-PACKAGE :cl-user)
(ccl:make-socket :remote-host "ya.ru" :remote-port 443 :address-family :IRRELEVANT)

另一种方法是*warn-if-redefine-kernel*在重新定义 make-socket 之前覆盖全局变量。

(setf *warn-if-redefine-kernel* nil)

这将避免持续的错误信号,并直接修补内核函数。

于 2017-09-07T19:59:10.673 回答