
Lisp Macros by Example - stopachka
https://m.stopa.io/macros-by-example-6ddbc8f3d93b
======
yori
Really good choice of examples. Makes sense to someone coming from Java,
JavaScript or Python background. I have bookmarked this already and I am going
to send this article to my friends and colleagues to show them the power of
Lisp macros.

By the way, what kind of development environment should I suggest to others
who want to get started with Lisp? Very few, if any, use Emacs. Some use Vim.
Most use Visual Studio Code. What kind of development environment would be
most suitable to this kind of demographics?

~~~
jlg23
For CL, the quasi-standard is emacs and slime, probably followed by the vim-
equivalent.

~~~
yori
Thanks for the suggestions. Anyone here has used the Vim equivalents? There
are a few choices like slimv, vlime, vim-slime. What's the difference between
them? Are they as good as emacs and slime?

~~~
davidsierradz
You can check this previous hn thread:
[https://news.ycombinator.com/item?id=21735148](https://news.ycombinator.com/item?id=21735148)

------
lordleft
This article really makes me want to learn lisp. Is there a risk that lisp’s
extensibility creates lots of very specific dialects of lisp that can be
difficult to share / understand?

~~~
elwell
Clojure best-practices warn against using macros when a function would do. It
tends to complicate debugging and maintainability. However, macros are very
powerful, and when they are the right tool for the job they can create magic
and afford new ways of looking at a problem.

On the other end of the spectrum, Racket[0] fully embraces the DSL-creating
power of macros.

To answer your question: I think that lisp's extensibility can be used to
create something difficult to share/understand OR easier to share/understand
[1]. Whereas some other languages might be limited by their ability to
naturally express a domain-fitted solution, yet they make it easier to avoid
creating a cryptic mess. So... lisps don't confuse people, people do.

[0] - [https://racket-lang.org/](https://racket-lang.org/)

[1] - a good example of macro use is Compojure's use of a macro for http route
definitions.
[https://github.com/weavejester/compojure#usage](https://github.com/weavejester/compojure#usage)

------
kccqzy
It should be noted that the first example can be implemented in C as well:

    
    
        static inline void *null_throws_helper(void *p, const char *msg) {
          if (!p) {
            fputs("uh oh: ", stderr);
            fputs(msg, stderr);
            fflush(stderr);
            abort();
          }
          return p;
        }
        #define nullthrows(arg) null_throws_helper((arg), #arg)
    

In C with the GNU statement expression extension, the above can be more
concisely written:

    
    
        #define nullthrows(arg)                                                        \
          ({                                                                           \
            void *p = (arg);                                                           \
            if (!p) {                                                                  \
              fputs("uh oh: ", stderr);                                                \
              fputs(#arg, stderr);                                                     \
              fflush(stderr);                                                          \
              abort();                                                                 \
            }                                                                          \
            p;                                                                         \
          })
        
    

With C++, you can do better by preserving the original type of pointer, or
throw an actual exception rather than terminating the program.

The C/C++ preprocessor macro system isn't great, but it's good enough to be
really useful. For great design, I'd lean towards the hygienic syntax-rules
system used by Scheme.

~~~
bjoli
Complex macros needs to allow breaking hygiene unless you have a particularly
masochistic stroke. I am a very big fan of r6rs syntax-case which I find is
the sweet spot between the comfort of being able to break hygiene and still
being able to trust your macro output is not shadowing any bindings.

~~~
jlg23
In CL one uses _gensym_ to introduce a macro-local symbol, or, for multiple
symbols, _with-gensyms_ as provided by the Alexandria library. No need for
unhygienic macros at all.

~~~
bjoli
Defmacro can be implented in 7 lines of syntax case, and gensym is in syntax
case as well. The difference is that CL has you jumping through hoops to have
something resembling hygiene, whereas it is default in scheme.

It is one of many ideological differences, and I dont understand why that one
has become so divisive.

------
bloopernova
I'm not a very good programmer, but this article was really well laid out and
easy to read. I'm slowly learning Lisp to hack on Emacs and org-mode/org-
babel, and I wish more people wrote stuff like this article!

~~~
stopachka
This made my day to read. Thank you :)

------
perfunctory
This snippet doesn't look right

    
    
      macro nullthrows(sourceCodeSnippet) {
        return code`
          const result = ${sourceCodeSnippet}; 
          if (result === null || result === undefined) {
            return result
          } else {
            throw new Error("Uh oh, this returned null:" + "${sourceCodeSnippet}");
        `;
      }
    

The first and the second occurrences of ${sourceCodeSnippet} look the same so
I suppose it will just be evaluated twice. You don't want to evaluate the
second occurrence, right? The lisp version doesn't have this problem.

~~~
perfunctory
In this snippet parentheses are in the wrong place

    
    
      (defmacro nil-throws [form]
        `(let [result# ~form] ;; assign the evaluation of form to result#
            (if (nil? result#)
              (throw
                (ex-info "uh oh, we got nil!" {:form '~form}) ;; save form for inspection
              result#))))
    

I guess, should be

    
    
      (defmacro nil-throws [form]
        `(let [result# ~form] ;; assign the evaluation of form to result#
            (if (nil? result#)
              (throw
                (ex-info "uh oh, we got nil!" {:form '~form})) ;; save form for inspection
              result#)))

~~~
stopachka
Great catch! Updated, thank you :)

------
nickkell
I understand that it's a powerful feature, but the fact that its overuse is so
strongly warned against, and (apparently) so rarely needed makes me wonder if
in practice it is that useful.

How often do rank-and-file lisp developers independently discover reusable
programming abstractions that are only possible via macros? Or does the main
benefit lie in not having to wait for the designers of your programming
language to implement a feature that could be a library?

I would really appreciate some more stories of how people use macros in their
day-to-day work!

~~~
perfunctory
> Or does the main benefit lie in not having to wait for the designers of your
> programming language to implement a feature that could be a library?

Precisely. And you are right, it's very hard to write useful, robust, reusable
macros.

------
pmoriarty
This article is almost but not quite readable without enabling javascript in
the browser. All of the code examples are missing from the non-javascript
version.

~~~
stopachka
Thanks for the feedback pmoriarty!

What kind of code examples would you have liked to see?

~~~
pmoriarty
Just the examples you already have in the article.

For example, for "Example 1: nullthrows" there's no code for that example that
I can see without enabling javascript in my browser.

Or when you write: "Here's how we could implement that as a function in
javascript:" there's no code that I can see there either.

It's the same for every other code block in the article. All invisible without
javascript.

~~~
stopachka
Ah, understood, thank you for clarifying!

Indeed, this is an unfortunate side-effect for medium: the only way to show
code examples is to embed github gists.

May end up moving to some other platform.

~~~
LandR
Another downside of the gists is that it breaks in reader mode. The code
samples just don't appear at all :(

~~~
stopachka
Indeed!

I plan on moving off of medium. Hacking with the folks at OneGraph to create a
blog in a pretty novel way. Stay tuned!

------
npr11
Nice article! If you like this, you might like the Julia language. It has
"lisp macros" but generally has "python-like" syntax

Edit: here's a link to docs
[https://docs.julialang.org/en/v1/manual/metaprogramming/inde...](https://docs.julialang.org/en/v1/manual/metaprogramming/index.html#man-
macros-1)

------
tzs
Missing closing parenthesis:

    
    
      createBill(addToCart(cart, updatePrice(item, 100))

~~~
stopachka
Updated, thanks tzs!

------
BoiledCabbage
Great relatable description with a motivating example.

~~~
stopachka
Thank you for the kind words :)

