我是一个 lisper 初学者,我决定编写一个 Telegram 机器人作为测试项目。在这里我遇到了一些意想不到的行为
let
。该代码采用 .asd 系统的形式,其中一个文件包含以下代码。其中需要注意的重要部分:
long-poll-updates
函数基本上是一个循环。
在
read-updates
函数中调用
long-poll-updates
在
check-integrity
函数中调用 read-updates
。
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 时,但有过编码经验时的任何情况) )。我觉得这非常奇怪,但这不是重点。
问题是发布的代码正在修改文字对象,即
checks
是列表文字,setf
用于修改它。但根据HyperSpec:
如果文字对象被破坏性修改,后果是不确定的。
在这种情况下,
checks
的存储似乎被重用,这是一个完全合法的优化,因为您不应该改变绑定到checks
的列表文字的存储。
解决问题的一个简单方法是仅使用
(list :has-ok nil :is-ok nil :has-results nil)
,因为 list
创建一个新列表。