1

I've been reading sicp trying to understand scheme, specially macros. I noticed that sicp doesnt talk about macros at all. I read on Paul Graham's site that:

The source code of the Viaweb editor was probably about 20-25% macros. Macros are harder to write than ordinary Lisp functions, and it's considered to be bad style to use them when they're not necessary. So every macro in that code is there because it has to be.

So I desperately wanted to know how to write macros and what to use them for, so I read this site about macros: http://www.willdonnelly.net/blog/scheme-syntax-rules/ But that site just explains how to write a "for" macro. I think Paul Graham talks about CL and the other blog talks about scheme, but they are partly the same. So, wat can be an example of something that cannot be done with ordinary procedures and thus must be done with macros?

EDIT: I have already seen a similar question, but wanted to ask if there was some sort of crazy algorithm that you could do with macros that couldn't possible be done with procedures(other than syntactic sugar as described in that question's answer).

Community
  • 1
  • 1
twkmz
  • 419
  • 1
  • 6
  • 14

3 Answers3

4

Macros are a tricky subject that I've personally reversed my own opinions on no less than a dozen times, so take everything with a grain of salt.

If you are just getting acquainted with macros, you'll find the most helpful to be those macros that clarify or expedite existing expressions.

One such macro that you may have been exposed to is the anaphoric macro, which remains as popular today as the day Paul Graham coined the term:

(define-syntax (aif x)
  (syntax-case x ()
    [(src-aif test then else)
     (syntax-case (datum->syntax-object (syntax src-aif) '_) ()
       [_ (syntax (let ([_ test]) (if (and _ (not (null? _))) then else)))])]))

These macros introduce "anaphora" variables, namely it that you can use in the consequent and alternative clauses of an if statement, e.g:

(aif (+ 2 7)
  (format nil "~A does not equal NIL." it)
  (format nil "~A does equal NIL." it))

This saves you the hassle of typing a let statement -- which can add up in large projects.

More broadly, transformations that change the structure of a program, dynamically generate "templated" code or otherwise "break" rules (that you hopefully know well enough to break!) are the traditional domain of the macro. I could write on and on about how I've simplified countless projects and assignments with macros -- but I think you'll get the idea.

Learning Scheme-style macros is a little daunting, but I assure you that there are excellent guides to syntax-rules-style macros for the merely eccentric, and as you gain more and more experience with macros, you'll probably come to the conclusion that they are a beautiful idea, worthy of the name "Scheme".

If you happen to use Racket (previously known as PLT Scheme) -- It has excellent documentation on macros, even providing a few neat tricks along the way (I'd also guess most of what is written there can be readily written in another Scheme dialect without issue)

zetavolt
  • 2,989
  • 1
  • 23
  • 33
  • Let me see if I understood. Macros can be an adavnced subject and require a lot of scheme knowledge but they allow the programmer to simplify scheme's syntax? Because I had this idea that macros were some black magic spells that made lsip unique, instead of making lisp more readable(in which case I think it doesn't make much sense to use lisp at all) – twkmz Jul 15 '16 at 01:33
  • 1
    No, it's not about simplifying the language, it's about extending it. That is something you simply cannot do in most other languages. Have a look at this, for example: http://synthcode.com/scheme/match.scm Think about *keywords* in other languages. If a certain keyword does not exist, you cannot add it. In Scheme, or Lisp in general, you can. – Michael Vehrs Jul 15 '16 at 05:26
  • I think extending the language is what really matters here. That's what most other languages can't do. Usually, in other language we can change the behaviour of some keywords but we can't add more. For example, operator overriding in C++. – Loïc Faure-Lacroix Jul 15 '16 at 11:16
  • Think I got it. I still have a lot of reading to do, but I think I'm beginning to understand what's going on here. Thanks a lot! – twkmz Jul 15 '16 at 11:27
2

Here is one standard answer (part of which is also covered in SICP - see Exercise 1.6 for example; also search for the keyword "special form" in the text): in a call-by-value language like Scheme, an ordinary procedure (function) must evaluate its argument(s) before the body. Thus, special forms such as if, and, or, for, while, etc. cannot be implemented by ordinary procedures (at least without using thunks like (lambda () body), which are introduced for delaying the evaluation of the body and may incur performance overheads); on the other hand, many of them can be implemented with macros as indeed done in RnRS (see, for instance, the definition of and by define-syntax on page 69).

FPstudent
  • 831
  • 5
  • 9
1

The simple answer is that you can make new syntax where delaying evaluation is essential for the functionality. Imagine you want a new if that works the same way as if-elseif. In Lisp it would be something that is like cond, but without explicit begin. Example usage is:

(if* (<= 0 x 9) (list x)
     (< x 100)  (list (quotient x 10) (remainder x 10))
     (error "Needs to be below 100"))

It's impossible to implement this as a procedure. We can implement something that works the same by demanding the user to give us thunks and thus the parts will become procedures that can be run instead:

(pif* (lambda () (<= 0 x 9)) (lambda () (list x))
      (lambda () (< x 100)) (lambda () (list (quotient x 10) (remainder x 10)))
      (lambda () (error "Needs to be below 100")))

Now it's easy implementation:

(define (pif* p c a . rest)
  (if (p)
      (c)
      (if (null? rest)
          (a)
          (apply pif* a rest))))

This is how JavaScript and almost every other language that doesn't support macros do it. Now if you want to give the user the power of making the first instead of the second you need macros. Your macro can write the first to the second so that your implementation can be a function or you can just change the code to a nested if:

(define-macro if*
  (syntax-rules ()
    ((_ x) (error "wrong use of if*"))
    ((_ p c a) (if p c a))
    ((_ p c next ...) (if p c (if* next ...)))))

Or if you want to use the procedure it's even simpler to just wrap each argument in a lambda:

(define-syntax if*
 (syntax-rules ()
   ((_ arg ...) (pif* (lambda () arg) ...)))

The need of macros is to reduce boilerplate and simplify syntax. When you see the same structure you should try to abstract it into a procedure. If that is impossible since the arguments are used in special forms you do it with a macro.

Babel, a ES6 to ES5 transpiler converts JavaSript ES6 syntax to ES5 code. If ES5 had macros it would have been as easy as making compability macros to support ES6. In fact most features of newer versions of the language would have been unnecessary since programmers don't have to wait for a new version of a language to give it new fancy features. Almost nothing of new language features in Algol languages (PHP, Java, JavaScript, Python) would have been necessary if the language had hygenic macro support.

Sylwester
  • 47,942
  • 4
  • 47
  • 79