方案:用于代码复制的宏或高阶函数?
我想获取调用 shell 命令并返回字符串的函数的结果。我正在使用球拍,这是我的第一次尝试:
(define (run-function)
(let*
([stdout (some-function)]
[output (process-string stdout)])
;; many more lines...
output))
它似乎工作得很好,但假设我想编写类似于run-function许多其他 shell 命令的函数。
为了避免代码重复,我可以定义一个更通用的函数,如下所示:
(define (shell cmd)
(let*
([stdout (cmd)]
[output (process-string stdout)])
;; many more lines...
output))
然后调用例如(shell ls)或(shell pwd)。
另一种方法是使用一个简单的宏:
(define-syntax shell
(syntax-rules ()
[(shell cmd)
(let*
([stdout (cmd)]
[output (process-string stdout)])
;; many more lines...
output)]))
这也有允许使用更通用语法的优点,例如我可以轻松更改宏,以便根据需要使用尽可能多的参数(命令),但我确信可以通过编写更高的顺序来复制相同的行为更明智地发挥作用。
问:编写高阶函数与宏的优缺点是什么?两者之间有明显的赢家吗?
回答
我同意@MLavrentyev 所说的,“如果可以,请使用函数”。
和球拍风格指南还指出:
尽可能定义函数,或者,当函数可以使用时不要引入宏。
但为什么?一个原因是,如果你写成shell一个函数,你可以传递shell给其他函数。您可以通过标识符宏功能对宏执行相同的操作,但这样做要困难得多(无论如何您最终都会有效地创建一个函数)。
另一个原因是使用宏会使编译后的代码比使用函数更大。这是因为宏在编译时被扩展,然后扩展的代码被编译(在 Racket BC 中编译为字节码,在 Racket CS 中编译为机器代码)。所以就好像你(let* ...)一遍又一遍地写。如果您分发已编译或可执行的 Racket 程序,您不会希望它的大小很大。
事实上,编写宏时的一个好习惯是尽量将代码填充到函数中。而不是写:
(define-syntax-value (debug code)
(let ([val code])
(printf "~a evaluates to ~an" (quote code) val)
val))
最好写成:
(define (debug-core expr val)
(printf "~a evaluates to ~an" expr val)
val)
(define-syntax-value (debug code)
(debug-core (quote code) code))