Lisp Macros, Delayed Evaluation and the Evolution of Smalltalk
Lisp user here.  I&#x27;ll chime in with another example of using macros for more than just delaying evaluation.<p>I wrote a library called Chancery[1] for procedurally generating strings (and other data).  It&#x27;s inspired by Tracery[2] but takes advantage of macros to make it easier to read and feel more like part of the language.  I use it to write stupid Twitter bots like <a href="https:&#x2F;&#x2F;twitter.com&#x2F;git_commands" rel="nofollow">https:&#x2F;&#x2F;twitter.com&#x2F;git_commands</a> and <a href="https:&#x2F;&#x2F;twitter.com&#x2F;rpg_shopkeeper" rel="nofollow">https:&#x2F;&#x2F;twitter.com&#x2F;rpg_shopkeeper</a> for fun.<p>As an example let&#x27;s say we want to generate a message about the loot we receive from a monster in a fantasy, D&amp;D-style story.  Maybe we&#x27;ll start with some random weapons:<p><pre><code>    (chancery:define-string weapon-type
      &quot;sword&quot;
      &quot;spear&quot;
      &quot;lance&quot;
      &quot;flail&quot;
      &quot;mace&quot;)

    (weapon-type) ; =&gt; &quot;mace&quot;
    (weapon-type) ; =&gt; &quot;sword&quot;
</code></pre>
This expands like so:<p><pre><code>    (macroexpand-1
      &#x27;(chancery:define-string weapon-type
        &quot;sword&quot;
        &quot;spear&quot;
        &quot;lance&quot;
        &quot;flail&quot;
        &quot;mace&quot;))
    ; =&gt;
    (DEFUN WEAPON-TYPE ()
      (CASE (CHANCERY::CHANCERY-RANDOM 5)
        (0 &quot;sword&quot;)
        (1 &quot;spear&quot;)
        (2 &quot;lance&quot;)
        (3 &quot;flail&quot;)
        (4 &quot;mace&quot;)))
</code></pre>
This simple case actually <i>could</i> be done just by delaying evaluation, as long as we get every body clause as a separate thunk.  Now let&#x27;s define a rule for generating the material of a weapon:<p><pre><code>    (chancery:define-string (weapon-material :distribution :weighted)
      (100 &quot;iron&quot;)
      (40 &quot;steel&quot;)
      (5 &quot;silver&quot;)
      (4 &quot;gold&quot;)
      (1 &quot;adamantine&quot;))
</code></pre>
This will generate the materials according to a weighted distribution, and macroexpands to:<p><pre><code>    (DEFUN WEAPON-MATERIAL ()
      (CASE (CHANCERY::WEIGHTLIST-RANDOM #&lt;CHANCERY::WEIGHTLIST ((100 0) (40 1) ...)&gt;)
        (0 &quot;iron&quot;)
        (1 &quot;steel&quot;)
        (2 &quot;silver&quot;)
        (3 &quot;gold&quot;)
        (4 &quot;adamantine&quot;)))
</code></pre>
This case needs more than just delayed evaluation.  If you receive `(100 &quot;iron&quot;)` as an opaque thunk, where all you can do is evaluate it, there&#x27;s no way to pull out the weight and body components.<p>If we add a few more rules, we can see more cases where we need to go beyond delayed evaluation:<p><pre><code>    (defun currency-amount ()
      (+ 10 (random 100)))

    (chancery:define-string (currency-type :distribution :zipf)
      &quot;copper&quot;
      &quot;silver&quot;
      &quot;gold&quot;
      &quot;platinum&quot;)

    (chancery:define-string loot
      #((weapon-material weapon-type) chancery:a)
      (currency-amount currency-type &quot;coins&quot;))

    (chancery:define-string discovery
      (&quot;You open the chest and find&quot; loot :. &quot;.&quot;)
      (&quot;You find&quot; loot &quot;in the monster&#x27;s hidden stash.&quot;)
      (&quot;You find nothing but dust and cobwebs.&quot;))

    (discovery) ; =&gt; &quot;You find nothing but dust and cobwebs.&quot;
    (discovery) ; =&gt; &quot;You find an iron sword in the monster&#x27;s hidden stash.&quot;
    (discovery) ; =&gt; &quot;You find 61 copper coins in the monster&#x27;s hidden stash.&quot;
    (discovery) ; =&gt; &quot;You open the chest and find a steel sword.&quot;
</code></pre>
Macroexpanding the last one:<p><pre><code>    (DEFUN DISCOVERY ()
      (CASE (CHANCERY::CHANCERY-RANDOM 3)
        (0 (CHANCERY::JOIN-STRING &quot;You open the chest and find&quot;
                                  (PRINC-TO-STRING #\ )
                                  (LOOT)
                                  &quot;.&quot;))
        (1 (CHANCERY::JOIN-STRING &quot;You find&quot;
                                  (PRINC-TO-STRING #\ )
                                  (LOOT)
                                  (PRINC-TO-STRING #\ )
                                  &quot;in the monster&#x27;s hidden stash.&quot;))
        (2 (CHANCERY::JOIN-STRING &quot;You find nothing but dust and cobwebs.&quot;))))
</code></pre>
Here we can see the macro walking the lists and doing different things to each element: strings are included raw, symbols are turned into function calls, and the special keyword :. suppresses the usual joining space character inserted between everything.  There&#x27;s also some special handling of vectors, in the LOOT example, which I won&#x27;t go into.  This is more than just delayed evaluation — we&#x27;re inspecting the actual structure of the code received by the macro at macroexpansion time.  If all we had were an opaque thunk that we could evaluate later, we couldn&#x27;t do this.<p>Delayed evaluation is enough for certain kinds of abstraction, like writing basic control structures, but isn&#x27;t as powerful as full macros.  Macros let you transform arbitrary code into other arbitrary code using the full power of the language.<p>[1]: <a href="https:&#x2F;&#x2F;sjl.bitbucket.io&#x2F;chancery&#x2F;" rel="nofollow">https:&#x2F;&#x2F;sjl.bitbucket.io&#x2F;chancery&#x2F;</a>
[2]: <a href="http:&#x2F;&#x2F;tracery.io&#x2F;" rel="nofollow">http:&#x2F;&#x2F;tracery.io&#x2F;</a>