Reputation: 279
I would like to get the results of a function which calls a shell command and returns a string. I am using racket, and this is my first attempt:
(define (run-function)
(let*
([stdout (some-function)]
[output (process-string stdout)])
;; many more lines...
output))
It seems to work well enough, but suppose that I would like to write functions similar to run-function
for many other shell commands.
To avoid code duplication, I could just define a more general function like this:
(define (shell cmd)
(let*
([stdout (cmd)]
[output (process-string stdout)])
;; many more lines...
output))
and then call for example (shell ls)
or (shell pwd)
.
An alternative would be using a simple macro:
(define-syntax shell
(syntax-rules ()
[(shell cmd)
(let*
([stdout (cmd)]
[output (process-string stdout)])
;; many more lines...
output)]))
This would also have the advantage of allowing a more general syntax, for example I could easily change the macro so that it takes as many parameters (commands) as I want, but I am sure the same behaviour could be replicated by writing the higher order function more sensibly.
Q. What are the pros/cons of writing a higher order function vs a macro? Is there a clear winner between the two?
Upvotes: 4
Views: 243
Reputation: 6502
I agree with what @MLavrentyev said, "use a function if you can".
And the Racket style guide also says:
Define functions when possible, Or, do not introduce macros when functions will do.
But why? One reason is that if you write shell
as a function, you can pass shell
to other functions. You can do the same with macros via the identifier macro feature, but it's much more difficult to do so (and you will effectively end up creating a function anyway).
Another reason is that using macros will make compiled code larger than using functions. This is because macros are expanded at compile-time, and the expanded code is then compiled (into bytecode in Racket BC, machine code in Racket CS). So it's as if you write (let* ...)
over and over again. If you distribute the compiled or executable Racket program, you wouldn't want the size to be large.
In fact, a good practice when writing a macro is to try to stuff code into functions as much as possible. Instead of writing:
(define-syntax-value (debug code)
(let ([val code])
(printf "~a evaluates to ~a\n" (quote code) val)
val))
it would be better to write it as:
(define (debug-core expr val)
(printf "~a evaluates to ~a\n" expr val)
val)
(define-syntax-value (debug code)
(debug-core (quote code) code))
Upvotes: 6