eg1 - 使用let
(defun demo1 ()
(let ((a 1)
(b 2))
; these print fine
(print a)
(print b)))
(demo1)
; these get "no value" errors, as expected
(print a)
(print b)
输出:
1
2 *** - EVAL: variable A has no value
EG2 - 没有let
,变量转义
(对于习惯于更现代的范围规则(例如 Ruby's)的人来说,这是非常令人惊讶的)
(defun demo2 ()
(setf a 1)
(setf b 2)
; these print fine
(print a)
(print b))
(demo2)
; these escape, and also print with no error!
(print a)
(print b)
输出:
1
2
1
2
你怎么能让他们不逃脱?
我和setq
一起胡思乱想,defvar
(在我通过查找有关"局部变量"的文档可以找到的唯一结果中提到了这些结果)
但一点也不快乐
EG3 - 尝试使用宏
(这就是我当初想要解决的实际问题——let
的语法迫使你使用额外的参数层,
并将整个函数体包裹在最外层,
这只会使无缘无故
的读写变得更加困难(因为绝大多数最常见的用例是let
始终包括整个函数体,仅此而已),
所以我想制作一个with
宏
)
(defmacro with (&rest list-of-pairs)
(loop for (name value) in list-of-pairs
do `(setf ,name ,value) ;A
; do (setf name value) ;B
; (i tried both A and B)
))
(defun demo3 ()
(with (a 1)
(b 2))
; this gets a "no value" error when called
(print a)
(print b))
(demo3)
; it never even gets to this point cuz of the above error
(print a)
(print b)
输出:
*** - PROGN: variable A has no value
如何让变量转义到函数范围而不是超出函数范围?
[这个问题问
谁能告诉我如何在 LISP 中定义除 let 之外的局部变量?
但是没有一个答案对我没有
帮助]
编辑添加 eg4
思考loop
宏的工作方式
(从某人在不了解其内部的情况下调用它的角度来看,我的意思是)......
好吧,看:
(loop for i from 1 to 5
do (print i))
我还不知道loop
的定义是什么样子的,
但它抽象地是这样的,对吧?
(defmacro loop ([args somehow,
including the `sexpr`
which goes after the `do` keyword in the macro call])
[other stuff]
do ([sexpr])
[other stuff])
(我以 do
关键字为例,只是因为调用的语法相对简单。
所以我实际上需要做的是制作我自己的my-defun
宏
并包括一个with
关键字,
右?
像这样:
(defmacro my-defun ([args somehow,
including a `paired-list-of-names-and-values`
to go after a `with` keyword in the macro call])
(let
([paired-list-of-names-and-values])
([function body])))
(my-defun demo4 ()
with (
(a 1)
(b 2)
)
; this should print
(print a)
(print b))
(demo4)
; this should get a "no value" error
(print a)
(print b)
我在这里走在正确的轨道上吗?
如果是这样,我该何去何从?
比如,我可以看看哪些简单、直接的宏定义来诱导它们是如何工作的?
或类似的东西
简单的规则:SETF
或SETQ
不创建变量。既不是本地的也不是全球性的。他们只是设置变量。
切勿使用 SETQ
和 SETF
设置未定义的变量。这是Common Lisp,而不是Ruby
。
使用宏创建SETF
窗体也无济于事。这为什么要有所作为?
定义局部变量
如果你研究一下Common Lisp标准,有无数的结构允许定义局部变量:DEFUN
,DEFMETHOD
,LET
,LET*
,DO
,DOTIMES
,FLET
,LABELS
,LAMBDA
,DESTRUCTURING-BIND
,MULTIPLE-VALUE-BIND
,...
函数中的局部变量
如果您查看函数,函数的参数列表允许您定义局部变量:
- 必需参数
- 可选参数
- 关键字参数
- 辅助参数
- 休息参数
您可以使用 LAMBDA
、 DEFUN
、
&optional
示例:
((lambda (a &optional (b 20))
... ; a and b are known here inside the function
)
10) ; we don't need to pass an arg for `b`, optional!
&aux
示例:
((lambda (a &aux (b (+ a 20)))
... ; a and b are known here inside the function
)
10) ; we CAN't pass an arg for `b`, auxiliary!
循环中的变量
你不需要猜测 LOOP 宏是做什么的,你可以让 Lisp 向你展示 - 这里使用 LispWorks:
CL-USER 27 > (pprint (macroexpand '(loop for i from 1 to 5
do (print i))))
(BLOCK NIL
(MACROLET ((LOOP-FINISH () '(GO #:|end-loop-82961|)))
(LET ((I 1)
(#:|to-82964| 5)
(#:|by-82965| 1))
(TAGBODY (PROGN (SYSTEM::INTERNAL-IF (OR (> I #:|to-82964|))
(GO #:|end-loop-82961|)))
#:|begin-loop-82960|
NIL
(PRINT I)
(PROGN
(LET ((#:|temp-82966| (+ I #:|by-82965|)))
(SETQ I #:|temp-82966|))
(SYSTEM::INTERNAL-IF (OR (> I #:|to-82964|))
(GO #:|end-loop-82961|)))
(GO #:|begin-loop-82960|)
#:|end-loop-82961|
(RETURN-FROM NIL NIL)))))
如您所见,它扩展为一个表单,其中变量i
由LET
引入。它还扩展为一个窗体,该窗体使用 goto 构造。
正如你所看到的,你可以在Lisp中实现一个非常花哨的语法 - 比如LOOP语法。但是LOOP的实现是大而不平凡的。对于初学者来说没什么。
我碰巧喜欢 Lisp 中引入绑定的方式,而在其他语言中我很想念它。但是,我不会尝试根据自己的喜好更改它们。相反,我遵循其他语言的特定习语。在评论中,您问:
你是说我应该能够使用你玩我的信息来做到这一点,还是说这是不可能的?
事实上,Rainer Joswig 告诉你,你不应该这样做,原因与你不要在 C 代码中引入BEGIN
和END
宏来替换大括号的原因相同:只需使用现有语言而不是直接尝试"修复"它。
顺便说一下,请理解LET
的现有设计不是偶然的,并且按预期工作。"从现代的角度来看"(参见 OCaml/F#)对您来说看起来很奇怪的事实并不意味着它是错误的或设计不当的。我不知道你说的以下评论是什么意思:
它可以正确地为传递给函数的参数隐藏和取消阴影变量定义
。但我可以告诉你,这没有多大意义。在尝试修改 Lisp 之前,请查阅 Common Lisp 编程的在线教程,以便更好地了解 Lisp。
Rainer 确实在上面介绍了这一点,但可能没有专门针对您的示例:函数定义的 &optional 参数(defun 或 lambda)似乎正是您正在寻找的(我相信,确实是作为围绕 let 的宏实现的)。
(defun do-stuff (&optional (a 10) (b 20))
(print a)
(print b))
(do-stuff)
10
20
20 <-- return value as last form evaluated in defun
(print a) --> ERROR; it has not escaped.
如果您只是想尝试实现宏,那么对您很好!
[编辑:
我刚刚意识到我在参数处理上搞砸了,
所以我的"解决方案"坏了
如果参数列表之后没有至少三个列表提供给my-defun
。
我把(我认为)实际的工作解决方案放在最后。
]
我居然想通了!
一点也不难,
即使是新手。
(我的意思是
如果真的试图在"真实代码"中使用它,如果发生可怕的事情,我不会感到惊讶,
因为边缘情况什么的,
但这是一个工作的概念
验证)
无论如何,这是my-defun
宏的定义,
正如我在eg4
中描述的问题中所述:
(请不要编辑奇怪的格式 -
我意识到这是非标准的,
但它确实可以帮助新手阅读困难的新东西。
我的意思是
如果另一个像我这样的新手读过这篇文章,
我认为这将对他们有很大帮助。
)
(defmacro my-defun (name params withsymbol withlist &body body)
(cond
((equal withsymbol 'with)
; (print 'withsymbol_set_to_) ;diagnostic
; (print withsymbol) ;diagnostic
; (print 'withlist_set_to_) ;diagnostic
; (print withlist) ;diagnostic
; (print ;diagnostic
`(defun ,name ,params
(let ,withlist
(progn ,@body)
)
)
; ) ;diagnostic
)
(t
; (print 'withsymbol_not_set_to_with_but_) ;diagnostic
; (print withsymbol) ;diagnostic
; (print 'withlist_set_to_) ;diagnostic
; (print withlist) ;diagnostic
; (print ;diagnostic
`(defun ,name ,params
(progn ,withsymbol ,withlist ,@body)
)
; ) ;diagnostic
)
)
)
第一次测试,with
:
(my-defun demo4 (x)
with (
(a 1)
(b 2)
)
; this prints!
(print a)
(print b)
(print x)
)
(demo4 "hi")
; this correctly gets a "no value" error!
(print a)
(print b)
(print x)
输出:
1
2
"hi"
未注释诊断行的输出:
WITHSYMBOL_SET_TO_
WITH
WITHLIST_SET_TO_
((A 1) (B 2))
(DEFUN DEMO4 (X) (LET ((A 1) (B 2)) (PROGN (PRINT A) (PRINT B) (PRINT X))))
1
2
"hi"
第二次测试,没有with
:
(所以它的行为与普通defun
完全一样)
(my-defun demo4 (x)
; (this stuff also prints)
(print "i am not the withsymbol")
(print "this is not the withlist")
; this prints!
(print "symbol 'a would have no value")
(print "symbol 'b would have no value")
(print x)
)
(demo4 "hi")
; this correctly gets a "no value" error!
'(print a)
'(print b)
'(print x)
输出:
"i am not the withsymbol"
"this is not the withlist"
"symbol 'a would have no value"
"symbol 'b would have no value"
"hi"
未注释诊断行的输出:
WITHSYMBOL_NOT_SET_TO_WITH_BUT_
(PRINT "i am not the withsymbol")
WITHLIST_SET_TO_
(PRINT "this is not the withlist")
(DEFUN DEMO4 (X)
(PROGN (PRINT "i am not the withsymbol") (PRINT "this is not the withlist") (PRINT "symbol 'a would have no value")
(PRINT "symbol 'b would have no value") (PRINT X)))
"i am not the withsymbol"
"this is not the withlist"
"symbol 'a would have no value"
"symbol 'b would have no value"
"hi"
最小差异的例子:
将defun
与let
一起使用并将my-defun
与with
一起使用(只是想盯着结果在多大程度上值得麻烦xD)
( defun demo (x)
(let (
(a 1)
(b 2)
)
(print a)
(print b)
(print x)
)
)
(my-defun demo (x)
with (
(a 1)
(b 2)
)
(print a)
(print b)
(print x)
)
实际有效的解决方案(我希望):
(defmacro fun (name params &rest rest)
(let (
(withsymbol (car rest))
(withlist (car (cdr rest)))
(body (cdr (cdr rest)))
)
; (p withsymbol ) ;;debug
; (p withlist ) ;;debug
; (p body ) ;;debug
(cond
((equal withsymbol 'with)
; (print 'BRANCH_A) ;;debug
; (print ;;debug
`(defun ,name ,params
(let* ,withlist
(progn ,@body)
)
)
; ) ;;debug
)
(t
; (print 'BRANCH_B) ;;debug
; (print ;;debug
`(defun ,name ,params
(progn ,@rest)
)
; ) ;;debug
)
)
)
)
;; for debugging
(defmacro p (symbol)
`(format t "~A ~A~%" ',symbol ,symbol)
)
虽然这是代码的最早工作版本,所以也许我在没有注意到的情况下通过不完整地重命名变量或其他东西搞砸了它。
我实际上刚刚测试的最新代码更复杂:
;; used in debug
(defmacro p (symbol)
`(format t "~A ~A~%" ',symbol ,symbol))
(defmacro mac-or-fun (which-one name params rest)
(let (
(withsymbol (car rest))
(withlist (car (cdr rest)))
(body (cdr (cdr rest)))
)
; (p withsymbol ) ;;debug
; (p withlist ) ;;debug
; (p body ) ;;debug
(cond
((equal withsymbol 'with)
; (print 'BRANCH_A) ;;debug
; (print ;;debug
`(,which-one ,name ,params
(let* ,withlist
(progn ,@body)
)
)
; ) ;;debug
)
((equal withsymbol 'omwith)
; (print 'BRANCH_A) ;;debug
; (print ;;debug
`(,which-one ,name ,params
(omlet ,withlist
(progn ,@body)
)
)
; ) ;;debug
)
(t
; (print 'BRANCH_B) ;;debug
; (print ;;debug
`(,which-one ,name ,params
(progn ,@rest)
)
; ) ;;debug
)
)
)
)
(defmacro fun (name params &rest rest)
`(mac-or-fun defun ,name ,params ,rest))
(defmacro mac (name params &rest rest)
`(mac-or-fun defmacro ,name ,params ,rest))
;; for use in tests
(defun ps (&rest stringlist)
(format t "~A~%" (eval `(concatenate 'string ,@stringlist))))
(defparameter *vs-use-count* 0)
(defmacro vs (&rest title)
(setf *vs-use-count* (+ 1 *vs-use-count*))
(ps "
SECTION " (write-to-string *vs-use-count*) " " (write-to-string title) " -"
)
)
;;;tests
(progn
(vs fun works with "with")
(fun f ()
with ((a 1))
(print a)
)
(f)
(vs fun works with "nil")
(fun f ()
()
)
(print(f))
(vs original fun test with "with")
(fun demo4 (x)
with (
(a 1)
(b 2)
)
; this prints!
(print a)
(print b)
(print x)
)
(demo4 "hi")
; these would correctly gets a "no value" error!
'(print a)
'(print b)
'(print x)
(vs original fun test with no "with")
(fun demo4 (x)
; (this stuff also prints)
(print "i am not the withsymbol")
(print "this is not the withlist")
; this prints!
(print "symbol 'a would have no value")
(print "symbol 'b would have no value")
(print x)
)
(demo4 "hi")
; these would correctly gets a "no value" error!
'(print a)
'(print b)
'(print x)
(vs mac works with "with")
(mac m ()
with ((a 1))
(print a)
)
(m)
(vs mac works with "nil")
(mac m ()
()
)
(print(m))
)
;;; more stuff,
;;; leading up to the macro `omlet`,
;;; which is used in `mac-or-fun`
(fun pair-up (l)
with (
(a (car l) )
(b (car (cdr l)) )
(l-past-b (cdr (cdr l)) )
)
(cond
(
(equal 2 (length l))
(list l)
)
(t
(cons (list a b) (pair-up l-past-b))
)
)
)
(fun crack-1 (eggs)
with (
(paired-list (pair-up eggs))
(paired-list (loop for (k v) in paired-list collect `(,k ',v)))
)
paired-list
)
(fun crack-2 (eggs)
with (
(key-name (car eggs))
(value-name (car (cdr eggs)))
(eggs (cdr (cdr eggs)))
(paired-list (pair-up eggs))
(keys-list (loop for pair in paired-list collect (first pair)))
(values-list (loop for pair in paired-list collect (second pair)))
(key-name-keys (list key-name keys-list))
(value-name-values (list value-name values-list))
(paired-list (append paired-list (list key-name-keys value-name-values)))
(paired-list (loop for (k v) in paired-list collect `(,k ',v)))
)
paired-list
)
(fun crack (eggs)
(if
(and
(equal '- (car eggs))
(oddp (length eggs))
)
(crack-2 (cdr eggs))
(crack-1 eggs)
)
)
(mac omlet (eggs &body body)
with ((to-let (crack eggs)))
`(let ,to-let (progn ,@body))
)
(mac lemego (&rest eggs)
with ((to-set (crack eggs)))
(loop for (name value) in to-set
do (eval `(setf ,name ,value))
)
)
;;; more tests
(progn
(vs omlet 1)
(omlet (
x a
y b
z c
)
(print x )
(print y )
(print z )
)
(vs omlet 2)
(omlet (
- names digits
one 1
two 2
three 3
)
(print one )
(print two )
(print three )
(print names )
(print digits )
)
(vs fun with omwith 1)
(fun f ()
omwith (
x a
y b
z c
)
(print x )
(print y )
(print z )
)
(f)
(vs fun with omwith 2)
(fun f ()
omwith (
- names digits
one 1
two 2
three 3
)
(print one )
(print two )
(print three )
(print names )
(print digits )
)
(f)
(vs lemego 1)
(lemego
x a
y b
z c
)
(print x )
(print y )
(print z )
(vs lemego 2)
(lemego
- names digits
one 1
two 2
three 3
)
(print one )
(print two )
(print three )
(print names )
(print digits )
)