具有相同名称的通用Lisp本地阴影功能 [英] Common Lisp locally shadow function with same name

查看:95
本文介绍了具有相同名称的通用Lisp本地阴影功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题我已经不止一次了。

I've had this question more than once before.

是否可以透明地局部遮盖函数 f 和它的包装与同一个名称 f

Is it possible to transparently locally shadow a function f with a wrapper of it with the same name f?

即,如何在本地将(f wraped-args ...)扩展为(f args ...)?

I.e., how to locally have (f wrapped-args...) expand to (f args...)?

Flet似乎允许我们这样做,但有局限性,即结果包装器不可设置。

Flet seems to let us do so, but has limitations, namely, the resulting wrapper is not setf-able. Is it possible to do so without resorting to flet?

理想情况下,会有一个宏可以让我们编写包裹的宏。 f 调用,它将代码扩展为原始的非包装代码。 f 调用。

Ideally there would be a macro that lets us write the "wrapped" f calls and it expands the code to the original "non-wrapped" f call.

起初,我相信宏可能是那样,因为它在文档中说先扩展宏,然后扩展宏在展开的表单上应用setf,但我无法使用它(请在下面继续阅读)。

At first I believed macrolet could be that, for it says in the documentation that it first expands the macro and then applies setf on the expanded form, but I'm not being able to use it (keep reading below).

对于更多的DRY代码,某些参数是隐式的,不应一遍又一遍地重复。

This is useful in contexts where some paremeters are implicit and should not be repeated over and over, for more DRY code.

我之前的问题(let-curry),其中有一个具体示例。尝试自动执行以下操作:分配一些函数的参数(let-curry)。

In my previous question (let-curry) there's a particular example of that. Attempting to "automatically" assign some of the parameters of the functions (let-curry).

我在那里得到了一些很好的答案,但是,我点击了一些限制。通过诉诸于flet来完成这样的本地影子操作,将函数名称包装到其上的包装器中,此类包装器不可设置,因此,此类包装器无法像原始功能一样灵活地使用,只能读取值而不是写入。

I got some excellent answers there, however, I hit some limitations. By resorting to flet to accomplish such local "shadowing" of the function name to a wrapper over it, such wrappers are not setf-able, thus, such wrappers cannot be used as flexibly as the original function, only to read values, not write.

通过上面的链接,如何编写宏flet-curry并使包装函数可设置?

With the link above, how can one write the macro flet-curry and have the wrapper functions be setf-able?

奖金:该宏可以在0的运行时开销下将包装的调用扩展为原始调用吗?

Bonus: Can that macro expand the wrapped calls to the original ones with 0 runtime overhead?

我尝试在该帖子中获取选定的答案,并使用macrolet而不是flet无效。

I tried taking the selected answer in that post and using macrolet instead of flet to no avail.

谢谢!

我被要求举一个具体的例子

I was asked to give a concrete example for this generic question.

代码中的愿望注释:

(locally (declare (optimize safety))
  (defclass scanner ()
    ((source
      :initarg :source
      :accessor source
      :type string)
     (tokens
      :initform nil
      :accessor tokens
      :type list)
     (start
      :initform 0
      :accessor start
      :type integer)
     (current
      :initform 0
      :accessor current
      :type integer)
     (line
      :initform 1
      :accessor line
      :type integer))
    (:metaclass checked-class)))

(defun lox-string (scanner)
  "Parse string into a token and add it to tokens"
  ;; Any function / defmethod / accessor can be passed to let-curry

  ;; 1. I'd like to add the accessor `line` to this list of curried methods:
  (let-curry scanner (peek at-end-p advance source start current)
    (loop while (and (char/= #\" (peek))
                     (not (at-end-p)))
          do
             ;; 2. but cannot due to the incf call which calls setf:
             (if (char= #\Newline (peek)) (incf (line scanner))
                 (advance)))
    (when (at-end-p)
      (lox.error::lox-error (line scanner) "Unterminated string.")
      (return-from lox-string nil))
    (advance) ;; consume closing \"
    (add-token scanner 'STRING (subseq (source)
                                       (1+ (start))
                                       (1- (current))))))

意思是我想要 let-curry 来转换任何对curried函数的调用从

Meaning I'd like let-curry to transform any call of the curried functions in that block from


  1. (f arg1 arg2 ...)

  2. (f扫描仪arg1 arg2 ...)

  1. (f arg1 arg2 ...) to
  2. (f scanner arg1 arg2 ...)

就位,就好像我在源代码中写的是后一种形式,而不是前一种形式。如果某些宏是这种情况,那么它可以通过设计设置。

in place, as if I'd written the latter form and not the former in the source code. If that were the case with some ?macro?, then it would be setf-able by design.

似乎宏是解决此问题的正确工具,但我不知道如何

It seems a macro would be the right tool for this but I don't know how.

再次感谢:)

PS:如果需要访问完整代码,请访问: https://github.com/AlbertoEAF/cl-lox (scanner.lisp)

P.S.: If you need access to the full code it's here: https://github.com/AlbertoEAF/cl-lox (scanner.lisp)

推荐答案

macrolet 绑定并不容易,因为:

Binding with macrolet is not trivial since:


  • f 绑定到宏中后,如果它扩展为(f ...) ,您将拥有无限的宏扩展。

  • 此外,您可以将宏扩展为(应用#'f ...)(这很好,因为 APPLY 可以是SETF位置 1 ),但是然后出现错误因为#'f 绑定到本地宏,而不是原始函数。但是,如果首先评估#'f ,将其绑定到隐藏变量,然后定义一个应用该变量值的宏,则SETF APPLY会抱怨(至少在SBCL中)

  • Once you bind f in a macrolet, if it expands as (f ...), you are going to have infinite macroexpansion.
  • Also, you could expand the macrolet as (apply #'f ...) (which is great, since APPLY can be a SETF place1), but then you have errors because #'f is bound to a local macro, not the original function. If, however, you first evaluate #'f, bind it to a hidden variable, then define a macro that applies the variable's value, SETF APPLY complains (at least in SBCL) that the function must not be a symbol (ie. dynamically computed).

1 :例如 (let((x(列表0 1 2)))(prog1 x(setf(应用#'第二个列表())9)))

但是您不需要宏,因为您可以绑定 SETF 函数在 FLET 中;如果要在本地重新定义某些功能,可以使用以下代码手动编写:

But you don't need macrolet, since you can bind SETF functions in FLET; here is what you could write manually if you wanted to redefine some functions locally:

(defun lox-string (scanner)
  (flet 
    ((peek        ()  (peek scanner))
     (at-end-p    ()  (at-end-p scanner))
     (advance     ()  (advance scanner))
     (line        ()  (line scanner))
     ((setf line) (n) (setf (line scanner) n))
     (source      ()  (source scanner))
     (start       ()  (start scanner))
     (current     ()  (current scanner)))
    (loop 
       while (and (char/= #\" (peek))
                  (not (at-end-p)))
       do
         (if (char= #\Newline (peek)) 
         (incf (line))
             (advance)))
    (when (at-end-p)
      (error "Unterminated string at line ~a" (line)))
    (advance)
    (add-token scanner 'STRING (subseq (source)
                                       (1+ (start))
                                       (1- (current))))))



展开为FLET



以下宏扩展为不可破坏的flet,并以特殊方式处理 SETF 函数,因为第一个参数始终是设置的值:

Expand as FLET

The following macro expands as inlinable flets and handles SETF functions in a special way, since the first argument is always the value being set:

(defmacro with-curry ((&rest fn-specs) prefix &body body)
  (loop 
     with args = (gensym)
     and n = (gensym)
     and prefix = (alexandria:ensure-list prefix)
     for f in fn-specs
     collect (if (and (consp f) (eq 'setf (first f)))
                 `(,f (,n &rest ,args) (apply #',f ,n ,@prefix ,args))
                 `(,f (&rest ,args) (apply #',f ,@prefix ,args))) 
     into flets
     finally (return
               `(flet ,flets
                  (declare (inline ,@fn-specs))
                  ,@body))))

例如:

(let ((scanner (make-instance 'scanner)))
  (with-curry (start (setf start)) scanner
    (setf (start) (+ (start) 10))))

此宏扩展为:

(LET ((SCANNER (MAKE-INSTANCE 'SCANNER)))
  (FLET ((START (&REST #:G849)
           (APPLY #'START SCANNER #:G849))
         ((SETF START) (#:G850 &REST #:G849)
           (APPLY #'(SETF START) #:G850 SCANNER #:G849)))
    (DECLARE (INLINE START (SETF START)))
    (LET* ((#:NEW1 (+ (START) 10)))
      (FUNCALL #'(SETF START) #:NEW1))))



内联FLET



内联声明是一个 request (编译器可能会忽略它)用函数主体替换对函数的每次调用(参数由函数调用参数替换;在lambda演算中看起来像β减少)。

Inlining FLET

The inline declaration is a request (the compiler may ignore it) to replace each calls to the function by its body (parameters are substituted by the function call arguments; it looks like β-reduction in lambda-calculus).

当编译器识别出它时,就像您将代码定义为宏一样,无需调用函数。当内联生效时, apply 将在编译期间看到要调用的函数对象和所有参数,因此,如果直接编写所有参数,则编译器可以发出代码。

When the compiler recognizes it, it is as-if you defined the code as a macrolet, removing the need to call a function. When inlining is in effect, apply will see during compilation both the function object to call and all the arguments, so the compiler can emit code as-if you wrote directly all parameters.

让我们使用SBCL进行测试,首先使用 notinline 声明来明确防止内联:

Let's test that with SBCL, first with a notinline declaration to explicitly prevent inlining:

(disassemble
 (lambda ()
   (declare (optimize (debug 0) (safety 0)))
   (flet ((p (&rest args) (apply #'print args)))
     (declare (notinline p))
     (p 0) (p 1))))

反汇编程序的输出有点长,我不会声称我确切地知道会发生什么;有一个第一段显然分配了内存(用于本地功能?):

The output of the disassembler is a bit long, and I won't claim I understand what happens exactly; there is a first segment that apparently allocates memory (for the local function?):

; disassembly for (LAMBDA ())
; Size: 187 bytes. Origin: #x53F0A5B6 (segment 1 of 2)        ; (LAMBDA ())
; 5B6:       49896D28         MOV [R13+40], RBP               ; thread.pseudo-atomic-bits
; 5BA:       4D8B5D68         MOV R11, [R13+104]              ; thread.alloc-region
; 5BE:       498D4B10         LEA RCX, [R11+16]
; 5C2:       493B4D70         CMP RCX, [R13+112]
; 5C6:       0F878C000000     JNBE L8
; 5CC:       49894D68         MOV [R13+104], RCX              ; thread.alloc-region
; 5D0: L0:   498D4B07         LEA RCX, [R11+7]
; 5D4:       49316D28         XOR [R13+40], RBP               ; thread.pseudo-atomic-bits
; 5D8:       7402             JEQ L1
; 5DA:       CC09             INT3 9                          ; pending interrupt trap
; 5DC: L1:   C7410117001050   MOV DWORD PTR [RCX+1], #x50100017  ; NIL
; 5E3:       488BDD           MOV RBX, RBP
; 5E6:       488D5424F0       LEA RDX, [RSP-16]
; 5EB:       4883EC10         SUB RSP, 16
; 5EF:       48891A           MOV [RDX], RBX
; 5F2:       488BEA           MOV RBP, RDX
; 5F5:       E82F000000       CALL L4
; 5FA:       49896D28         MOV [R13+40], RBP               ; thread.pseudo-atomic-bits
; 5FE:       4D8B5D68         MOV R11, [R13+104]              ; thread.alloc-region
; 602:       498D4B10         LEA RCX, [R11+16]
; 606:       493B4D70         CMP RCX, [R13+112]
; 60A:       775A             JNBE L9
; 60C:       49894D68         MOV [R13+104], RCX              ; thread.alloc-region
; 610: L2:   498D4B07         LEA RCX, [R11+7]
; 614:       49316D28         XOR [R13+40], RBP               ; thread.pseudo-atomic-bits
; 618:       7402             JEQ L3
; 61A:       CC09             INT3 9                          ; pending interrupt trap
; 61C: L3:   C641F902         MOV BYTE PTR [RCX-7], 2
; 620:       C7410117001050   MOV DWORD PTR [RCX+1], #x50100017  ; NIL
; 627:       EB03             JMP L5
; 629: L4:   8F4508           POP QWORD PTR [RBP+8]

...随后是第二段看起来像它实际上定义并调用了本地函数(?):

... followed by a second segment which looks like it actually defines and call the local function (?):

; Origin #x53F0A62C (segment 2 of 2)                          ; (FLET P)
; 62C: L5:   488BF4           MOV RSI, RSP
; 62F: L6:   4881F917001050   CMP RCX, #x50100017             ; NIL
; 636:       7412             JEQ L7
; 638:       FF71F9           PUSH QWORD PTR [RCX-7]
; 63B:       488B4901         MOV RCX, [RCX+1]
; 63F:       8D41F9           LEA EAX, [RCX-7]
; 642:       A80F             TEST AL, 15
; 644:       74E9             JEQ L6
; 646:       CC0A             INT3 10                         ; cerror trap
; 648:       06               BYTE #X06                       ; BOGUS-ARG-TO-VALUES-LIST-ERROR
; 649:       04               BYTE #X04                       ; RCX
; 64A: L7:   488B053FFFFFFF   MOV RAX, [RIP-193]              ; #<FUNCTION PRINT>
; 651:       FF2425A8000052   JMP QWORD PTR [#x520000A8]      ; TAIL-CALL-VARIABLE
; 658: L8:   6A11             PUSH 17
; 65A:       FF142550000052   CALL QWORD PTR [#x52000050]     ; CONS->R11
; 661:       E96AFFFFFF       JMP L0
; 666: L9:   6A11             PUSH 17
; 668:       FF142550000052   CALL QWORD PTR [#x52000050]     ; CONS->R11
; 66F:       EB9F             JMP L2

无论如何,它与<$ c $的反汇编输出有很大不同c>内联情况:

(disassemble
 (lambda ()
   (declare (optimize (debug 0) (safety 0)))
   (flet ((p (&rest args) (apply #'print args)))
     (declare (inline p))
     (p 0) (p 1))))

此打印:

; disassembly for (LAMBDA ())
; Size: 45 bytes. Origin: #x540D3CF6                          ; (LAMBDA ())
; CF6:       4883EC10         SUB RSP, 16
; CFA:       31D2             XOR EDX, EDX
; CFC:       B902000000       MOV ECX, 2
; D01:       48892C24         MOV [RSP], RBP
; D05:       488BEC           MOV RBP, RSP
; D08:       B8C2283950       MOV EAX, #x503928C2             ; #<FDEFN PRINT>
; D0D:       FFD0             CALL RAX
; D0F:       BA02000000       MOV EDX, 2
; D14:       B902000000       MOV ECX, 2
; D19:       FF7508           PUSH QWORD PTR [RBP+8]
; D1C:       B8C2283950       MOV EAX, #x503928C2             ; #<FDEFN PRINT>
; D21:       FFE0             JMP RAX

上面的内容较短,可以直接调用print。
相当于手动进行内联的反汇编:

The above is shorter, and directly calls print. It is equivalent to the disassembly where inlining is done by hand:

(disassemble (lambda ()
               (declare (optimize (debug 0) (safety 0)))
               (print 0) (print 1)))

; disassembly for (LAMBDA ())
; Size: 45 bytes. Origin: #x540D4066                          ; (LAMBDA ())
; 66:       4883EC10         SUB RSP, 16
; 6A:       31D2             XOR EDX, EDX
; 6C:       B902000000       MOV ECX, 2
; 71:       48892C24         MOV [RSP], RBP
; 75:       488BEC           MOV RBP, RSP
; 78:       B8C2283950       MOV EAX, #x503928C2              ; #<FDEFN PRINT>
; 7D:       FFD0             CALL RAX
; 7F:       BA02000000       MOV EDX, 2
; 84:       B902000000       MOV ECX, 2
; 89:       FF7508           PUSH QWORD PTR [RBP+8]
; 8C:       B8C2283950       MOV EAX, #x503928C2              ; #<FDEFN PRINT>
; 91:       FFE0             JMP RAX

这篇关于具有相同名称的通用Lisp本地阴影功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆