Electric Coffee
Electric Coffee

Reputation: 12104

What's the purpose of macros?

Here my general focus is Scala and Lisp/Scheme macros, not exactly the ones in the C/C++/Obj-C

I just don't see the point.

The way I understand it, is that macros are there to extend the language. But so are functions.

I understand that some things can't be implemented cleanly due to some language limitations, and as such a macro needs to be the way to go. But a LOT of examples I see with macros seem to be things that are fairly simple to implement using normal functions.

So what exactly is the purpose? Clean macros or otherwise, Someone please enlighten me. If possible, please provide some example code of something that can be done in macros, but is impossible/hard to do with normal functions.

Upvotes: 6

Views: 827

Answers (3)

Svante
Svante

Reputation: 51501

There are three main purposes that macros are used for in Lisp (don't know about Scala):

  • Defining: something is created and directly registered at the appropriate place. Examples: defun, defgeneric, defclass (from the standard), deftable (from postmodern).
  • Unwind-protect wrappers: a state is temporarily modified and ensured to be modified back after completion of a task. This can be cumbersome to write repeatedly, so we create a shorthand. Example: with-open-file (standard), with-transaction (many database libraries).
  • Generation of other languages: for example, CL-WHO (HTML), Parenscript (JavaScript). By generating the code of other languages in Lisp forms, we can use macros for those other languages even if they do not have support for that themselves.

Concrete example: Java 7 introduced a shorthand for ensuring the closing of Closables in try-blocks:

try (SomeClosable foo = openFoo()) {
    foo.doSomething();
}

which can in Java 6 only be expressed roughly like this:

SomeClosable foo;
try {
    foo = openFoo();
    foo.doSomething();
} finally {
    if (foo != null && foo.isOpen()) {
        foo.close();
    }
}

Java developers had to wait for the language designers to implement this feature. A Lisp developer uses a little macro:

(defmacro with-open-foo ((var &rest options) &body body)
  `(let ((,var (open-foo ,@options)))
     (unwind-protect
         (progn ,@body)
       (when ,var (close ,var)))))

so that he can write

(with-open-foo (f :bar baz)
  (do-some-foo f)
  (and-something-else))

instead of

(let ((f (open-foo :bar baz)))
  (unwind-protect
      (progn
        (do-some-foo f)
        (and-something-else))
    (when f (close f))))

Upvotes: 1

Sylwester
Sylwester

Reputation: 48745

In both Common Lisp and Scheme most of the special syntax are indeed implemented in terms of other special syntax, thus macros.

For instance both Scheme and CL have if, cond and case but only if is a primitive syntax.

There is nothing special about macros defined by the standard and the ones you might make yourself. They can be made to behave and work just as good as primitives.

Macros has a cost of obfuscating and surprising the reader. Using common naming conventions like with-* might help a bit but one should never use a macro if a function/procedure can do the job or if the form will only be used in a few number of places.

Upvotes: 2

Eugene Burmako
Eugene Burmako

Reputation: 13048

There is a concise summary of what things have been done with Scala macros: http://scalamacros.org/paperstalks/2014-02-04-WhatAreMacrosGoodFor.pdf. To sum it up, macros are known to be good for: 1) code generation, 2) advanced static checks, 3) empowering domain-specific languages. Having macros in Scala to be type-based provides an additional and powerful twist to the capabilities typically found in macros in Lisp-like languages.

Some examples in the slides linked above can indeed be implemented without macros, but the result will be either lacking in some sense (e.g. in performance) or overly complicated for the users (e.g. because of heavyweight error messages). For example, typed channels for Akka could be conceivably implemented with pure implicits, but compilation speeds and understandability would suffer. Or, scala/async could be implemented as a compiler plugin, but then it would have to depend on internal compiler APIs and would be harder to distribute.

Of course, macros are not the silver bullet. There clearly are use cases when they are not the best choice, and this is something that's outlined in http://scalamacros.org/paperstalks/2014-03-01-MacrosVsTypes.pdf. What's curious, though, is that in a number of situations neither pure-macro, nor macro-less solutions, but rather carefully constructed hybrids end up being the best.

Upvotes: 7

Related Questions