(special form)

.MACRO procname :input1 :input2 ...

A macro is a special kind of procedure whose output is evaluated as instructions in the context of the macro's caller. .MACRO is like TO except that the new procedure becomes a macro.

Macros are useful for inventing new control structures comparable to REPEAT, IF, and so on. Such control structures can almost, but not quite, be duplicated by ordinary procedures. For example, here is an ordinary procedure version of REPEAT:

TO MY.REPEAT :num :instructions
  IF :num=0 [STOP]
  RUN :instructions
  MY.REPEAT :num-1 :instructions

This version works fine for most purposes, for example

MY.REPEAT 5 [PRINT "hello]

But it doesn't work if the instructions to be run include OUTPUT, STOP, or LOCAL. For example, consider this procedure:

  PRINT [Guess my secret word.  You get three guesses.]
  REPEAT 3 [TYPE "|?? | IF READWORD = "secret [PRINT "Right! STOP]]
  PRINT [Sorry, the word was "secret"!]

This procedure works as written, but if MY.REPEAT were used instead of REPEAT, it wouldn't work because the STOP would stop MY.REPEAT instead of stopping EXAMPLE as desired.

The solution is to make MY.REPEAT a macro. Instead of carrying out the computation, a macro must output a list containing instructions. The contents of that list are evaluated as if they appeared in place of the call to the macro. Here's a macro version of REPEAT:

.MACRO MY.REPEAT :num :instructions
  IF :num=0 [OUTPUT []]
  OUTPUT SENTENCE :instructions (LIST "MY.REPEAT :num-1 :instructions)

Every macro is an operation; all macros always output something. Even in the base case, MY.REPEAT outputs an empty instruction list.

To see how the MY.REPEAT macro works, let's take the example

MY.REPEAT 5 [PRINT "hello]

For this example, MY.REPEAT outputs the instruction list

[PRINT "hello MY.REPEAT 4 [PRINT "hello]]

FMSLogo then runs these instructions in place of the original invocation of MY.REPEAT; this prints "hello" once and invokes another repetition.

The technique just shown, although fairly easy to understand, is too slow because each repetition constructs an instruction list for evaluation. Another approach is to make MY.REPEAT a macro that works just like the non-macro version unless the instructions to be repeated includes OUTPUT or STOP:

.MACRO MY.REPEAT :num :instructions
  CATCH "repeat.catchtag [OUTPUT REPEAT.DONE RUNRESULT [REPEAT1 :num :instructions]]

TO REPEAT1 :num :instructions
  IF :num=0 [THROW "repeat.catchtag]
  RUN :instructions
  .MAYBEOUTPUT REPEAT1 :num-1 :instructions

TO REPEAT.DONE :repeat.result
  IF EMPTYP :repeat.result [OUTPUT [STOP]]
  OUTPUT LIST "output QUOTED FIRST :repeat.result

If the instructions do not include STOP or OUTPUT, then REPEAT1 reaches its base case and invokes THROW. As a result, MY.REPEAT's last instruction line outputs an empty list, so the second evaluation of the macro does nothing. But if a STOP or OUTPUT happens, then REPEAT.DONE outputs a STOP or OUTPUT instruction that is re-run in the caller's context.

The macro-defining commands have names starting with a dot because macros are an advanced feature of Logo. It's easy to get in trouble by defining a macro that doesn't terminate, or by failing to construct the instruction list properly.

LISP users should note that Logo macros are not special forms. That is, the inputs to the macro are evaluated normally, as they would be for any other procedure. It's only the output from the macro that's handled unusually.

Here's how you can implement LOCALMAKE with .MACRO:

.MACRO LOCALMAKE :name :value
  OUTPUT (LIST ("local  word "" :name  "apply ""make (LIST :name :value))

It's used this way:

  LOCALMAKE "myvar "hello
  PRINT :myvar

LOCALMAKE outputs the list:

[LOCAL "myvar apply "make [myvar hello]]

LOCALMAKE uses APPLY to avoid deciding whether the second input to MAKE requires a quotation mark before it. In this case it would--MAKE "myvar "hello--but the quotation mark would be wrong if the value were a list.

It's often convenient to use the ` operation to construct the instruction list:

.MACRO LOCALMAKE :name :value
  OUTPUT `[LOCAL ,[WORD "" :name] APPLY "make [,[:name] ,[:value]]]

On the other hand, ` is slow, since it's tree-recursive and written in Logo.

SourceForge.net Logo