8 ms·
Lisp Macros by Example
- BoiledCabbage 7y agoGreat relatable description with a motivating example.
- stopachka 7y agoThank you for the kind words :)
- oldmandutch 7y agoI think you got your forms backward in the first javascript macro. It’s returning result if null instead of throwing an error...
- lordleft 7y agoThis 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?
- jlg23 7y agoIn my experience, yes. The rule to follow is to keep it simple and explicit. I have seen web request handlers that used a list method combination to save one line of code per handler for the price of less experienced devs spending 15 minutes and multiple failed attempts to explain their own 3-line handler the very day after they wrote it (not making this up).
- pmoriarty 7y agoLisps are a highly readable family of languages, so once you know one Lisp it's pretty easy to read the rest, and very quick to get up to speed in them so you can write your own code in pretty much any other Lisp dialect. 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.
- zwerdlds 7y agoFrom what I've picked up, creating Domain-Specific Languages is one of the Lisp family's strong points. Whether its a good thing or a bad thing is another matter.
- elwell 7y agoClojure 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
- oldmandutch 7y agoShort answer is yes. Lisp can be as poorly written as any language. But it does give you more rope to hang yourself.
- stopachka 7y agoGreat to hear this has sparked your curiosity. 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
- dreamcompiler 7y agoActually yes. The biggest issue I've had with using Common Lisp on a software team is everybody building their own languages. The remedy for this is to agree on standard macros and practices that everyone will abide by. Easier said than done, since CL programmers all come to the table with their own particular ways of doing things.
- invisiblerobot 7y agoCreating languages in Racket https://queue.acm.org/detail.cfm?id=2068896 https://queue.acm.org/detail.cfm?id=2068896
- yori 7y agoReally 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?
- stopachka 7y agoGreat to hear, thank you for the kind words Yori :) 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. https://cursive-ide.com/ https://cursive-ide.com/
- jlg23 7y agoFor CL, the quasi-standard is emacs and slime, probably followed by the vim-equivalent.
- yori 7y agoThanks 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 7y agoYou can check this previous hn thread: https://news.ycombinator.com/item?id=21735148 https://news.ycombinator.com/item?id=21735148
- kccqzy 7y agoIt 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 7y agoComplex 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 7y agoIn 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.
- 7y ago
- pmoriarty 7y agoThis 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 7y agoThanks for the feedback pmoriarty! What kind of code examples would you have liked to see?
- pmoriarty 7y agoJust 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 7y agoAh, 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.
- perfunctory 7y agoThis 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.
- S4M 7y agoI think it's fine. 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.
- perfunctory 7y agoSince this is a pseudo code it's hard to tell what the intended semantics are, but if as you say "the source code is just treated as a string", then it's still not right. What if sourceCodeSnippet contains double quotes? OP conveniently uses single quotes in their example getUser(db, ‘billy’) The whole point of lisp macros is that code is not treated as a string.
- stopachka 7y agoHi perfunctory, thanks for the thoughtful read : ). 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.
- perfunctory 7y ago
- bloopernova 7y agoI'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 7y agoThis made my day to read. Thank you :)
- tzs 7y agoMissing closing parenthesis: createBill(addToCart(cart, updatePrice(item, 100))
- stopachka 7y agoUpdated, thanks tzs!
- npr11 7y agoNice 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/index.html#man-macros-1 https://docs.julialang.org/en/v1/manual/metaprogramming/inde...
- nickkell 7y agoI 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 7y ago> 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.