2 ms·
The obvious "killer feature" of LISP is macros. One concrete example where I saw the power of macros in a problem I was working with was when I saw how clean th
by ufo 11y ago
The obvious "killer feature" of LISP is macros. One concrete example where I saw the power of macros in a problem I was working with was when I saw how clean the parser generator library in Racket was.
Instead of being like yacc, which uses an external DSL to define the parser, with its own syntactic conventions, the Racket parset generator is built with macros. The reason yacc has to be a metaprogram instead of just a library is because of how the semantic actions are "chunks of code" which you can't really represent in C but in LISP its easy to represent "chunks of unevaluated code" in your program, which lets you define the parser generator as an embedded dsl implemented via macros.
Anyway, the reason I found this nice is that in Racket you can define your own macros to abstract over the repetitive parts of the grammar description. For example, in yacc you need to write multiple rules to describe a comma-separated list
/* Use left recursion for efficient stack use
and reverse at the end so the list is in
left-to-right order */
arglist:
arglistrevdeclsx { $$ = reverse($1); }
;
arglistrev:
/*empty*/ { $$ = NULL; }
| arglistrev ',' arg { $$ = cons($2, $1); }
;
and this pattern repeats for every other list you might have in the grammar. In yacc there is no alternative but to copy and paste this boilerplate but in Racket you could define a helper macro that would let you write grammars that look a bit like this:
;; create a non-terminal called arglist
;; that is a list of "arg"s, separated by commas
,@(makelist arglist ',' arg)
;; and this code can be reused:
,@(makelist decllist ',' identifier)