是的,就像是 C 的宏一样会有名字冲突的问题,C 里面可能会使用各种奇怪的名字来避免冲突。顺带一提,Scheme 等其他语言可能提供了所谓的卫生宏来避免类似的名字冲突。
是的。
可以了解一下卫生宏。
也许用 Guile 写 Emacs 的人也有可你类似的想法。
是的,就像是 C 的宏一样会有名字冲突的问题,C 里面可能会使用各种奇怪的名字来避免冲突。顺带一提,Scheme 等其他语言可能提供了所谓的卫生宏来避免类似的名字冲突。
是的。
可以了解一下卫生宏。
也许用 Guile 写 Emacs 的人也有可你类似的想法。
好的,感谢说明。其实怎样都好,只要理解、找到适合elisp的实现,没想当然写就够了。
我本来想说用 make-symbol 创建不同的 symbol 并借 symbol-plist 存储 symbol 属性,然后用某种 (sexp-query sexp `((:id xxx))) 选中两个不同id 的 lambda ,以编辑其 body 。结果一通折腾下来,发现 make-symbol 出的 lambda 是 invalid-function 。现在只能再说了。
写法一看用法, 比如:
(hello y) ;; => (lambda (y) (+ y y))
这应当算是这个宏的特性 (bushi)
比如类似的做法可以用来模拟 return
这种可以在循环里面退出的操作 (Common Lisp):
(defmacro loop* (&body body)
(with-gensym (block) ;; <=> (let ((block (gensym "BLOCK")))
`(block ,block
(flet ((return (value) (return-from ,block value))
(loop ,@body)))))
;; Example:
(loop for i below 10 do (when (evenp i) (return i)))
也可以看看 Common Lisp 里面的 gensym
函数的效果:
(gensym "FOO") ;; => '#:FOO123
效果都是为了防止因为符号重名冲突引起的不必要的宏 bug.
不过这么搞真的不是在滥用宏么? 感觉直接用闭包来写会更好读也更好理解 (当然, 实际代码估计会稍微复杂一些)
(defun hello (x) (lambda (y) (+ x y)))
应该看 intern
函数:
;; elisp
(eq (intern "foo") 'foo) ;; => t
make-symbol
应当和 CL 的 gensym
效果更像.
特性也好,bushi也好,只要能变着法子实现就行。毕竟任何项目一庞大起来总会有这种事情。这贴子之后,我得考虑写宏的时候全 make-symbol 了。
这个知道,不过我原来的需求是用 make-symbol 创建两个名字相同但(存在论意义上)不同的 symbol 。比如两个 lambda ,每个 lambda 有各自的 symboll-plist 。
听你的描述似乎不是什么困难的任务。好像又是经典 X-Y 问题。
可以详细讲讲,然后给个简单的也许不能运行的 demo。
ELISP> sexp
(lambda
(a)
(let
((b
(lambda
(b)
(+ a b))))
b))
ELISP> (sexp-query sexp `((:id 1)))
(lambda
(a)
(let
((b
(lambda
(b)
(+ a b))))
b))
ELISP> (sexp-query sexp `((:id 2)))
(lambda
(b)
(+ a b))
大概这样,以类似DOM操作的思路,编辑 sexp 。
现在 query 出来的 lambda 虽然不能直接求值,但是 read 之后还是可以的。
一种真正意义上的 code 等于 data 。(只是某种尝试)。
sexp 经下面生成。
(defmacro sn (n &rest p)
`(let ((n (make-symbol (symbol-name ',n))))
(setf (symbol-plist n) ',p)
n))
(setq sexp `(,(sn lambda :id 1) (a)
(let ((b (,(sn lambda :id 2) (b) (+ a b))))
b)))
;; =>
(lambda (a)
(let ((b (lambda (b) (+ a b))))
b))
如果有某种更“标准”的方法修改 symbol 的 plist 就好了。
实现这个功能之后的后续任务是什么呢?从表中提取代码吗?
看怎么用?这只是某种操作sexp的“基础设施”。
原来如此。
你好,这种一定要用 symbol 来表示 symbol 的想法是思维局限,用 clos class 会更好。
给每个 symbol 独立的 plist 和让 eq 对同名 symbol 一视同仁本就是相互矛盾的需求。不用 symbol 就没这问题了。
不区分代码和表示代码的数据也有问题,有些 lisp 代码是可以自修改的,直接用代码表示给 eval 用会使代码表示本身被运行时修改,是不好的。
虽然还是有点不太理解, 但是还是有一堆的方法可以解决的:
可以直接用 (setf plist)
:
;; elisp
(let ((plist (list :a 1 :b 2)))
(setf (getf plist :a) 3)
(setf (getf plist :c) 1)
plist)
或者可以考虑一下改写 eval
的方式而不是用 symbol-plist
? 比如改写代码为:
((lambda :id 1 :foo 'bar) (x)
((lambda :id 2 :bar 'foo) (y)
(lamda (z) ;; no attribute
#|body|#)))
然后实现一个 normalize
函数:
(defun atomize (elem)
(if (atom elem) elem (car elem)))
(defun normalize (expr)
(if (atom expr)
expr
(let* ((fn (atomize (first expr)))
(args (rest expr)))
(case fn
(lambda ;; add more escapes
`(lambda ,(first args) ,@(mapcar #'normalize (rest args))))
(t
(cons fn (mapcar #'normalize args)))))))
;;; Example
(normalize '(+ x y)) ;; => '(+ x y)
(normalize '((+ :attr1 "Example") x y)) ;; => '(+ x y)
(normalize '((lambda :id 1) (x) (+ 1 2))) ;; => '(lambda (x) (+ 1 2))
然后给 eval
做一个 :around
advice 在收到表达式前先 normalize
再计算. 核心思路就是给 s-exp 的数据和代码之间表示做一个转换.
(当然, 如 LdBeth 所说用 CLOS class 会更好
(不过你要用这种方式储存数据的话, 可以考虑看看类似用 S-expr 代替 JSON 和 XML 的实现?
感觉对 code = data 好像有些奇怪的理解? 不妨去看看 compose
, curry
, combine
之类的对函数操作组合的实现和用法? (直接组合函数不比废那老功夫生成代码结构轻松么?
(可以看看 Haskell 之类的函数式语言, 或者想要 Lisp 系的话, 可以看 SDF 这本书的第一章: software-design-for-flexibility_how-to-avoid-programming-yourself-into-a-corner_hanson_sussman : Christopher P. Hanson, Gerald J. Sussman : Free Download, Borrow, and Streaming : Internet Archive
嗯,是的。其实你可以把我思路理解为:我把symbol当xml的tag用了,且期望有一种符合lisp语法的代码/数据结构,而非如libxml-parse-xml-region那样产生出来的特定结构类型。
另外 lisp 的 interactive command editor,Interlisp 的 XEDIT (和 SEdit) 早就做过了,建议下个 https://interlisp.org 玩玩或是至读下 Interlisp 的文档
这个很像,容我看看。
好的,感谢老铁指引。
这个 XEDIT 看起来很香 (看了一个演示: https://www.youtube.com/watch?v=2qsmF8HHskg), 里面的自动代码缩进和代码块提示感觉可以移植到 Emacs 里面 (这样就可以不用强迫症缩进对齐 lisp 代码了)
代码块提示的话用 (setq show-paren-style 'expression)
可以差不多模拟, 但是如果要实现类似于自动缩进的功能的话, 感觉需要写一个 trivial-formatter
(GitHub - hyotang666/trivial-formatter: Code formatter for common lisp.)?
(等有空了再回来看看这个.
看了一下你的描述,看上去你希望把某些符号绑定的数据也作为数据的一部分。如果我们把sexp视为xml,那么每一个sexp的意义仅限于它所包含的符号自身。如果我们想解析这样的结构,应该把它作为一个符号组成的树去解析,符号的意义应该由实现标准,或者,更具体的,某个解析器去决定。如果你希望数据与解析器之间具备某种自反的关系,你想要的可能不是lisp,而是tree calculus