Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should macros have side effects?

Can (or should) a macro expansion have side effects? For example, here is a macro which actually goes and grabs the contents of a webpage at compile time:

#lang racket

(require (for-syntax net/url))
(require (for-syntax racket/port))

(define-syntax foo
  (lambda (syntx)
    (datum->syntax #'lex
                   (port->string
                     (get-pure-port
                       (string->url
                         (car (cdr (syntax->datum syntx)))))))))

Then, I can do (foo "http://www.pointlesssites.com/") and it will be replaced with "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\r\n\t <and so on>"

Is this good practice, or not? Am I garunteed that Racket will only run this code once? If I add a (display "running...") line to the macro, it only prints once, but I would hate to generalize from one example ...

PS - the reason I'm asking is because I actually think this could be really useful sometimes. For example this is a library which allows you to load (at compile-time) a discovery document from the Google API Discovery service and automatically create wrappers for it. I think it would be really cool if the library actually fetched the discovery document from the web, instead of from a local file.

Also, to give an example of a macro with a different kind of side effects: I once built a macro which translated a small subset of Racket into (eta-expanded) lambda calculus (which is, of course, still runnable in Racket). Whenever the macro finished translating a function, it would store the result in a dictionary so that later invocations of the macro could make use of that function definition in their own translations.

like image 511
Ord Avatar asked Oct 26 '12 05:10

Ord


People also ask

Should macros be avoided?

"Macros are unsafe, have no type checking and should be avoided whenever possible. An alternative to macro is inline function for some use cases."

When should we prefer macros over inline functions?

On compilers with gcc extensions, macros, unlike inline functions, can test whether their arguments can be evaluated as constants. If one wishes to have a "function" like reverse8bits(x) which computes a bit-reversed byte value, it may be best to use (((x&128)>>7)|((x&64)>>5)...)

Why are macros better?

Macro counting helps you understand where those calories are coming from and how they affect your body. It also helps you understand that not all calories are created equal. For example, let's say you have a calorie goal of 2,000 a day. One gram of protein is 4 calories.

What is side effect in an expression?

In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, which is to say if it has any observable effect other than its primary effect of returning a value to the invoker of the operation.


1 Answers

The short answer

It's fine for macros to have side effects, but you should make sure that your program doesn't change behavior when it's compiled ahead of time.

The longer answer

Macros with side effects are a powerful tool, and can let you do things that make programs much easier to write, or enable things that wouldn't be possible at all. But there are pitfalls to be aware of when you use side effects in macros. Fortunately, Racket provides all the tools to make sure that you can do this correctly.

The simplest kind of macro side effect is where you use some external state to find the code you want to generate. The examples you list in the question (reading Google API description) are of this kind. An even simpler example is the include macro:

#lang racket
(include "my-file.rktl")

This reads the contents of myfile.rktl and drops it in place right where the include form is used.

Now, include isn't a good way to structure your program, but this is a quite benign sort of side effect in the macro. It works the same if you compile the file ahead of time as if you don't, since the result of the include is part of the file.

Another simple example that's not good is something like this:

#lang racket
(define-syntax (show-file stx)
  (printf "using file ~a\n" (syntax-source stx))
  #'(void))

(show-file)

That's because the printf gets executed only at compile time, so if you compile your program that uses show-file ahead of time (as with raco make) then the printf will happen then, and won't happen when the program is run, which probably isn't the intention.

Fortunately, Racket has a technique for letting you write macros like show-file effectively. The basic idea is to leave residual code that actually performs the side effect. In particular, you can use Racket's begin-for-syntax form for this purpose. Here's how I would write show-file:

#lang racket
(define-syntax (show-file stx)
  #`(begin-for-syntax
      (printf "using file ~a\n" #,(syntax-source stx))))

(show-file)

Now, instead of happening when the show-file macro is expanded, the printf happens in the code that show-file generates, with the source embedded in the expanded syntax. That way your program keeps working correctly in the presence of ahead-of-time compilation.

There are other uses of macros with side effects, too. One of the most prominent in Racket is for inter-module communication -- because require doesn't produce values that the requiring module can get, to communicate between modules the most effective way is to use side effects. To make this work in the presence of compilation requires almost exactly the same trick with begin-for-syntax.

This is a topic that the Racket community, and I in particular, have thought a lot about, and there are several academic papers talking about how this works:

Composable and Compilable Macros: You want it when?, Matthew Flatt, ICFP 2002

Advanced Macrology and the Implementation of Typed Scheme, Ryan Culpepper, Sam Tobin-Hochstadt, and Matthew Flatt, Scheme Workshop 2007

Languages as Libraries, Sam Tobin-Hochstadt, Ryan Culpepper, Vincent St-Amour, Matthew Flatt, and Matthias Felleisen, PLDI 2011

like image 90
Sam Tobin-Hochstadt Avatar answered Sep 25 '22 00:09

Sam Tobin-Hochstadt