代码之家  ›  专栏  ›  技术社区  ›  Alteration Dream

Common Lisp-为什么“让”变量保持在新迭代中对它们所做的更改?

  •  1
  • Alteration Dream  · 技术社区  · 1 年前

    我是个口齿不清的初学者,我决定写一个Telegram机器人作为测试项目。在这里,我遇到了一些意想不到的行为 let 该代码采用.asd格式,其中一个文件包含以下代码。它的重要部分要注意:

    1. long-poll-updates 函数基本上是一个循环。

    2. 致电 read-updates 在里面 长轮询更新 功能

    3. 致电 check-integrity 在里面 阅读更新 功能。

    4. checks let形式的变量。

    在主循环的每次迭代中,我都会执行以下操作 (prin1 checks) 看到一些我很难抬头或理解的东西。在循环的第一次迭代中,它会打印出我分配给它的值。每次新的迭代都不会重新分配 nil 返回值,但使用前一次迭代操作留下的相同精确值。

    (defun long-poll-updates ()
        "Main loop. Repeatedly sends requests to get updates."
        ; Sets a variable for storing the last processed updates's ID.
        (let ((offset 0)) (loop
            (let* ((api-answer  (get-updates-request offset))
                   (parsed-plist (jonathan:parse 
                                 (flexi-streams:octets-to-string api-answer))))
    
                ;; Read response and modify offset parameter to get next updates.
                (let ((response-data (read-updates parsed-plist)))  
                    (when (getf response-data :has-results) 
                        (setf offset 
                            (1+ (getf response-data :last-update-id)))))))))
    
    (defun read-updates (response-plist)
        "Reads the incoming long poll response: 
         checks for response validity/errors, 
         proceeds to an appropriate action."
        (let ((response-data (check-integrity response-plist)))
                (cond ((getf response-data :has-ok)
                    (cond ((getf response-data :is-ok)
            ;; Evaluate updates on successful poll
                        (cond
                            ((getf response-data :has-results)
                                (setf (getf response-data :last-update-id) 
                                (eval-updates response-plist)))
                        (t
                            (log-data "No results received."))))
                    (t (log-errors response-plist))))
                (t (log-data "Received malformed JSON-response while long polling."))) 
            response-data))
    
    (defun check-integrity (response-plist)
        "Runs checks for valid JSON received, success/faliure and presence of new updates.
         Returns a plist of checks passed/failed."
        (let ((checks '(:has-ok nil 
                        :is-ok nil 
                        :has-results nil)))
            (prin1 checks)
    
            (loop :for  (indicator value) on response-plist by #'cddr
                  ;; If successful response:
                  :when (eql indicator :|ok|)
                  :do   (progn 
                            (setf (getf checks :has-ok) t)
                            (when (eql value t)
                                (setf (getf checks :is-ok) t)))
                  ;; If any results:
                  :when (and (eql indicator :|result|)
                         (listp value)
                         (< 0 (length value)))
                  :do   (setf (getf checks :has-results) t))
            checks))
    

    我想我希望每次调用函数时,函数的代码都会重新计算,甚至没有意识到这一点。然而,在代码运行之前,编译器似乎会将数据计算到某个永远不会离开内存的实体。有人能告诉我一个我在这里处理的更大的想法吗?

    我看到lisp的方式真的是“理论优先”,我很少发现人们以一种块状和杂乱的方式(或者当你刚接触lisp,但之前有编码经验时,无论是什么样子)来记录他们在处理它时所面临的问题。我觉得这很奇怪,但这不是重点。

    1 回复  |  直到 1 年前
        1
  •  1
  •   ad absurdum    1 年前

    问题是发布的代码正在修改一个文字对象。, checks 是一个列表文字和 setf 用于修改它。但根据 HyperSpec:

    如果对文字对象进行破坏性修改,则后果未定义。

    在这种情况下,似乎存储 检查 被重用,这是一个完全合法的优化,因为你不应该改变绑定到的列表文字的存储 检查 .

    解决这个问题的一个简单方法就是使用 (list :has-ok nil :is-ok nil :has-results nil) 自从 list 创建一个新的列表。

    顺便说一句,这在其他语言中并不罕见。例如,在C中,试图修改字符串文字会导致未定义的行为。一般来说,在尝试修改文字表示的对象之前,您应该始终了解您使用的语言是如何处理这些对象的,否则可能会出现类似的错误。