你能在没有 `let` 的情况下创建局部变量吗?

Can you create local variables without a `let`?

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 的任何人来说,这 非常 令人惊讶)

(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 

你怎么能让他们逃脱?
我和 setqdefvar
胡闹 (在我通过查找 "local variables" 上的文档可以找到的唯一结果中提到了这些结果)
但一点也不开心

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

如何让变量逃逸到函数范围内而不超出范围?

[
this question

can anyone tell me how to define a local variable in lisp other than let?

但是 none 的答案对我有帮助
]

编辑添加 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)

我走对了吗?

如果是这样,我从这里去哪里?

例如,我可以查看哪些简单、直接的宏定义来推断它们的工作原理?
或类似的东西

简单规则:SETFSETQ 不创建变量。既不是本地的也不是全球的。他们只是设置变量。

切勿使用 SETQSETF 设置未定义的变量。这是 Common Lisp,不是 Ruby.

使用宏创建 SETF 表单也无济于事。为什么这会有所作为?

定义局部变量

如果你查看 Common Lisp 标准,有无数的结构允许定义局部变量:DEFUNDEFMETHODLETLET*DODOTIMESFLETLABELSLAMBDADESTRUCTURING-BINDMULTIPLE-VALUE-BIND、...

函数中的局部变量

如果你查看函数,函数的参数列表允许你定义局部变量:

  • 必需参数
  • 可选参数
  • 关键字参数
  • 辅助参数
  • 休息参数

您可以使用 LAMBDADEFUN、...

定义函数

&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)))))

如您所见,它扩展为一种形式,其中变量 iLET 引入。它还扩展为一个表单,该表单使用 goto 结构。

如您所见,您可以在 Lisp 中实现一种非常奇特的语法——如 LOOP 语法。但是 LOOP 的实现是庞大且复杂的。初学者没什么。

我碰巧喜欢 Lisp 中引入绑定的方式,但我很怀念其他语言中的这种方式。 但是,我不会尝试根据自己的喜好更改它们。 相反,我遵循其他语言的特定习语。 在评论中,你问:

are you saying that I should be able to use the info you game me to do that, or are you saying that it's just not possible?

事实上,Rainer Joswig 告诉您您不应该 这样做,原因与您不介绍 BEGINEND 的原因相同C 代码中用于替换大括号的宏:只需使用现有语言,而不是直接尝试 "fix" 它。

顺便请大家理解,LET现有的设计并非意外,按预期工作。您 "from a modern perspective"(参见 OCaml/F#)看起来很奇怪的事实并不意味着它是错误的或设计不当。我不知道你的评论是什么意思:

it can properly shadow and unshadow variable definition for parameters passed in to a function

...但我可以告诉你,它没有多大意义。 在尝试修改 Lisp 之前,请参考 Online Tutorials for programming Common Lisp 以更好地了解它。

[编辑:
我刚刚意识到我搞砸了争论处理,
所以我的"solution"坏了
如果 my-defun 在其参数列表之后没有至少三个列表。

我把(我认为)实际可行的解决方案放在最后。
]

我真的想通了!

一点也不难,
即使是新手。

(
我是说,
如果有人真的尝试在 "real code"、
中使用它,如果可怕的事情发生,我不会感到惊讶 因为边缘情况或其他东西,
但这是一个有效的概念验证
)

无论如何,这里是 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" 

差异最小的示例:

使用 defunlet
并使用 my-defunwith
(只是想看看结果在多大程度上值得麻烦 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  )
)

Rainer 确实在上面提到了这一点,但也许没有专门针对您的示例:函数定义(defun 或 lambda)的 &optional 参数似乎正是您要查找的内容(并且实际上是作为 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.

如果您只是想尝试实现宏,那就太好了!