macros - 有没有办法混合 LISP 的宏 &optional 和 &key 参数?
问题描述
我想定义一个 LISP 宏,这样dolist
我就可以定义一个可选的输出参数。在以下案例研究中,将调用此宏doread
。它将从文件中读取行并返回以这种方式找到的行数。
(let ((lines 0))
(doread (line file lines)
;; do something with line
(incf lines)))
问题是让它lines
在上面的宏中工作
我可以用 &key 做我想做的事,但不能用 &optional &key (并且 &key 是必需的,因为我想控制文件的读取方式;例如使用read
orread-line
或其他)。
现在以下工作但以错误的方式工作。这里的out
参数必须是 a&key
而不是 &optional:
;; this way works...
(defmacro doread ((it f &key out (take #'read)) &body body)
"Iterator for running over files or strings."
(let ((str (gensym)))
`(with-open-file (,str f)
(loop for ,it = (funcall ,take ,str nil)
while ,it do
(progn ,@body))
,out)))
;; lets me define something that reads first line of a file
(defun para1 (f)
"Read everything up to first blank line."
(with-output-to-string (s)
(doread (x f :take #'read-line)
(if (equalp "" (string-trim '(#\Space #\Tab) x))
(return)
(format s "~a~%" x)))))
(print (para1 sometime)) ; ==> shows all up to first blank line
我想做的是以下内容(注意out
现在已移至&optional
:
(defmacro doread ((it f &optional out &key (take #'read)) &body body)
"Iterator for running over files or strings."
(let ((str (gensym)))
`(with-open-file (,str f)
(loop for ,it = (funcall ,take ,str nil)
while ,it do
(progn ,@body))
,out)))
如果可行,我可以做类似的事情。
(defun para1 (f)
"Print everything up to first blank line.
Return the lines found in that way"
(let ((lines 0))
(doread (x f lines :take #'read-line)
(if (equalp "" (string-trim '(#\Space #\Tab) x))
(return)
(and (incf lines) (format t "~a~%" x)))))
但我用&optional out
我得到
loading /Users/timm/gits/timm/lisp/src/lib/macros.lisp
*** - GETF: the property list (#'READ-LINE) has an odd length
解决方案
您不能混合&optional
并&key
期望只能传递关键字参数。但是,您可以定义允许与源关联的可选参数列表的语法。
例如:
(defpackage :so (:use :cl :alexandria))
(in-package :so)
(defmacro do-read ((entry source &optional result) &body body)
(destructuring-bind (source &key (take '#'read)) (ensure-list source)
(once-only (take)
`(loop
:with ,entry
:do (setf ,entry (handler-case (funcall ,take ,source)
(end-of-file () (loop-finish))))
(progn ,@body)
:finally (return ,result)))))
的语法DO-READ
可以写成:
(DO-READ (ENTRY [SOURCE|(SOURCE &KEY TAKE)] &OPTIONAL RESULT) . BODY)
这不是标准 Lisp 形式的不寻常语法(请参阅LET
lambda-lists 中的关键字 synaxdefstruct
等)。您可以添加更多关键字参数以及TAKE
.
评论
在宏中,我更喜欢发出 LOOP 关键字作为关键字,而不是宏定义包中的符号;否则,在对代码进行宏扩展时,您可能会得到以宏包为前缀的符号(即,
SO::WITH
而不是:WITH
),这很快就会变得不可读。返回 NIL from
READ-LINE
很好,但不是 fromREAD
,因为 NIL 可能是成功读取的值。一般来说,既然TAKE
是用户提供的,你不知道 NIL 是否是一个可接受的结果。这就是为什么我抓住END-OF-FILE
了。如果您想从其他来源读取信息,您还可以检查辅助返回值,或记录它们也表示条件。变量的
ENTRY
范围被扩展,因此RESULT
可以是ENTRY
它自己;在您的情况下,OUT
不能等于IT
,因为一旦退出循环,您将无法再访问它。这是一个小问题,但这可能很有用。我没有包括
WITH-OPEN-FILE
,以防您想从文件(流)以外的其他内容中读取。#'READ
引用,这在这里并不重要,而是在宏中养成的一个好习惯,以便您在评估时实际评估事物,而不是在宏扩展时。
例子
(with-input-from-string (in "abcdef")
(do-read (char (in :take #'read-char) char)
(print char)))
打印所有字符并返回#\f
。
(with-input-from-string (in (format nil "~{~a~%~}" *features*))
(let ((lines 0))
(do-read (line in lines)
(incf lines))))
打印字符串中的行数。
推荐阅读
- kubernetes - 为什么 Kubernetes 的 statefulset 不能均匀地运行 3 个 pod?
- c++ - 如何创建一个基于其输入创建不同数量变量的结构
- google-apps-script - 无法打开 Google xlsx 电子表格/Google Drive 权限也被阻止
- sql-server - 如何释放 SQL Server 中用于解析 XML 字段的内存
- reactjs - $r 在 React 开发工具中
- angular - 使用angular提交表单时如何通过HTTP post请求保存令牌本地存储
- api - 如何在 Vuejs 的上下文中使用 API 获取图表数据
- terraform - 调用函数“文件”失败:不存在文件 - Terraform
- vue.js - Quasar - 从 .env 获取 API 密钥到引导文件中
- python - 如何修复我的代码以使用 Python 3.7 插入 SQL Server