【发布时间】:2011-04-09 20:37:39
【问题描述】:
我对 Clojure/Lisp 宏不是很熟悉。我想写apply-recur 宏,它的含义与(apply recur ...) 相同
我想没有真正需要这样的宏,但我认为这是一个很好的练习。所以我在问你的解决方案。
【问题讨论】:
我对 Clojure/Lisp 宏不是很熟悉。我想写apply-recur 宏,它的含义与(apply recur ...) 相同
我想没有真正需要这样的宏,但我认为这是一个很好的练习。所以我在问你的解决方案。
【问题讨论】:
好吧,真的没有必要这样做,只是因为recur 不能接受可变参数(函数顶部的recur 需要一个最终的可序列参数,将所有参数分组传递最后一个必需的参数)。当然,这不会影响练习的有效性。
但是,有一个问题是“正确的”apply-recur 应该可以处理任意表达式返回的参数序列,而不仅仅是文字:
;; this should work...
(apply-recur [1 2 3])
;; ...and this should have the same effect...
(apply-recur (vector 1 2 3))
;; ...as should this, if (foo) returns [1 2 3]
(apply-recur (foo))
然而,诸如(foo) 之类的任意表达式的值通常在宏扩展时根本不可用。 (也许(vector 1 2 3) 可能被假定总是产生相同的值,但foo 在不同的时间可能意味着不同的东西(一个原因eval 不起作用),是let 绑定的本地而不是Var (eval 不起作用的另一个原因)等等)
因此,要编写一个完全通用的apply-recur,我们需要能够确定一个常规的recur 表单期望多少个参数,并将(apply-recur some-expression) 扩展为类似的东西
(let [seval# some-expression]
(recur (nth seval# 0)
(nth seval# 1)
...
(nth seval# n-1))) ; n-1 being the number of the final parameter
(如果我们正在处理可变参数,则最终的nth 可能需要为nthnext,这会出现类似于下一段中描述的问题。此外,添加断言也是一个好主意检查some-expression返回的seqable的长度。)
我不知道有任何方法可以在宏扩展时在代码中的特定位置确定 recur 的正确数量。这并不意味着一个不可用——这是编译器无论如何都需要知道的,所以也许有一种方法可以从其内部提取该信息。即便如此,任何实现这一点的方法几乎肯定都需要依赖未来可能会改变的实现细节。
因此结论是这样的:即使完全可以编写这样的宏(甚至可能不是这样),任何实现都可能非常脆弱。
作为最后的评论,写一个 apply-recur 只能处理文字(实际上 arg seq 的一般结构需要作为文字给出;参数本身 - 不一定,所以这个可以工作:(apply-recur [foo bar baz]) => (recur foo bar baz)) 会相当简单。我不会因为放弃解决方案而破坏练习,但作为提示,请考虑使用~@。
【讨论】:
apply 是一个将另一个函数作为参数的函数。 recur是一种特殊形式,不是函数,所以不能传递给apply。
【讨论】:
(apply recur ...),那么如何编写一个能捕捉到预期意义的宏”。跨度>