Lisp Macros by Example
m.stopa.io
m.stopa.io
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?
For a development environment, I would suggest Cursive — it’s a plug-in on top of IntelliJ, and can fit right in for someone used to IDEs.
I went from Common Lisp to Chicken Scheme to eLisp and found the transitions very painless.
I've also peeked at code in various other Lisp dialects, and they've always been very readable to me.
If you're asking instead about DSLs written in Lisp, then the relationship of those DSLs themselves to Lisp is up to the author of the DSL in question. They can make the DSL very similar to Lisp or very different. It's entirely up to them.
Most of the Lisp code I've seen has stuck pretty close to Lisp itself, which makes it easy to read for anyone who knows Lisp.
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/
[1] - a good example of macro use is Compojure's use of a macro for http route definitions. https://github.com/weavejester/compojure#usage
Reach out if you end up having any questions!
As to the risk of specific dialects being too difficult to understand: this isn’t the case in practice. There’s a few of reasons for this, but the main one is that, you can’t deviate from lisp’s simple syntax too much — as soon as you do, it’s no longer a lisp, and can’t use macros in the same way
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.
It is one of many ideological differences, and I dont understand why that one has become so divisive.
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.In the first occurrence, $(sourceCodeSnippet) is replaced in the code, and later evaluated (and the result of this evaluation will be passed to the const result).
In the second occurrence, it is replaced inside a string, so it doesn't get evaluated, but the source code is just treated as a string (and displayed in the error message).
Note that the macro nullthrows is just generating some code depending of sourceCodeSnippet, and this code will be evaluated later.
getUser(db, ‘billy’)
The whole point of lisp macros is that code is not treated as a string.I'll update that snippet to include something like a `stringify` function, to make the intent more clear.
In terms of the intended semantics: - In some ways my intent was to show these limitations: unless we get code as data structures, our macros would be very brittle.
(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#)))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!
Precisely. And you are right, it's very hard to write useful, robust, reusable macros.
What kind of code examples would you have liked to see?
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.
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.
I plan on moving off of medium. Hacking with the folks at OneGraph to create a blog in a pretty novel way. Stay tuned!
Edit: here's a link to docs https://docs.julialang.org/en/v1/manual/metaprogramming/inde...
createBill(addToCart(cart, updatePrice(item, 100))