3 ms·
You are hanging out in the wrong forums :-) The `defmacro` model is "simpler", true - but it is fragile and breaks down when you want to do complicated things.
by soegaard 4y ago
You are hanging out in the wrong forums :-)
The `defmacro` model is "simpler", true - but it is fragile and breaks down
when you want to do complicated things.
Check this blog post written by someone, who knows hos Common Lisp.
EVAL-WHEN considered harmful to your mental health
https://fare.livejournal.com/146698.html https://fare.livejournal.com/146698.html
- Jach 4y agoMore to the point of how simple (and easy) defmacro is compared to syntax-case and syntax-rules, I like another fare post: https://fare.livejournal.com/189741.html https://fare.livejournal.com/189741.html If you're as smart as fare, it "isn't too hard to translate it". Though I concede this case could be analogous to trying to write a Doubly Linked List in Rust, it's not enough to say definitively yea or nay. So I'd more like to see a concrete case you have in mind where the tradeoffs are squarely against CL. Like, the loop macro is more complicated than any I have written myself, but you can break it down, and it's not that bad -- I think Norvig's version is pretty neat to study: https://norvig.com/paip/loop.lisp https://norvig.com/paip/loop.lisp This project (and it's not the only one!) adding C syntax to CL https://github.com/y2q-actionman/with-c-syntax https://github.com/y2q-actionman/with-c-syntax I think is more complicated than loop, and is sort of where I'd put the level of "complicated things" at that I'd like to see an example from the Scheme world that clearly shows defmacro's deficiencies on some metrics. (Fewer bugs? Easier to add new features to? Shorter code? Faster performance either at compile time or runtime or both? Easier to understand or faster to implement for people with similar levels of skill in the language?)
- soegaard 4y agoThe final `syntax-case` version: (define-syntax (nest stx) (syntax-case stx () ((nest outer ... inner) (foldr (lambda (o i) (with-syntax (((outer ...) o) (inner i)) #'(outer ... inner))) #'inner (syntax->list #'(outer ...)))))) looks fine to me. Using #` and #, and defining `reduce` one could make a version that looks like the Common Lisp implementation. Note that the `nest` form just rearranges its input - so there is no problems getting the scope of identifiers correct. That is: we are well within the comfort zone of `defmacro`. With regards to the `syntax-rules` solution: Fare refers to "when I learned Scheme" and pre-R6RS there were no `syntax-case` in the specification. In practise all implemenations did have macro systems more expressive than `syntax-rules`, but it was fun to see whether something was possible with plain `syntax-rules`.
- Jach 4y agoRight, it's fine, and is a pretty basic macro. Doubly linked lists are pretty basic data structures too, even the Rust versions once you figure it out. I like your sibling comment making it look like the CL version. I still want to know in more detail though why you think that doing things this way instead of the CL way is less likely to be "fragile and break down" for the complicated stuff, it would help to have a specific complicated example to showcase. Perhaps the linked https://github.com/disconcision/fructure https://github.com/disconcision/fructure in another comment would be a good study? The author there claimed they might not have been able to manage with defmacro, maybe someone familiar with both could articulate the challenges in detail. Is it just an issue of some things benefit a lot from pattern matching, and if so, does using CL's Trivia system mitigate that at all (in the same way that using gensym+packages+Lisp-2ness can mitigate hygiene issues)? Or is it more when there's a point of complexity where you need to do code walking, and this is more straightforward in something like Racket?
- soegaard 4y agoIt's not about pattern matching: you can use pattern matching to destructure the input of a syntax transformer (macro) in Common Lisp too. Using pattern matching makes the macros easier to read though [but that is of course a matter of opinion]. The "special sauce" is being explicit about compile time vs runtime (aka using phases). The paper "Composable and Compilable Macros: You Want it When?" by Matthew Flatt describes why conflating runtime and compile time is a problem. http://www.cs.utah.edu/plt/publications/macromod.pdf http://www.cs.utah.edu/plt/publications/macromod.pdf
- soegaard 4y agoHere is a version that follows the Common Lisp solution. Since `reduce` is the standard library of Common Lisp, I implemented the case I needed. #lang racket #;(defmacro nest (&rest r) (reduce (lambda (o i) `(,@o ,i)) r :from-end t)) (begin-for-syntax (require racket/match) (define (reduce f xs) (match xs [(list x0 x1) (f x0 x1)] [(cons a d) (f a (reduce f d))]))) (define-syntax (nest r) (reduce (λ (o i) #`(#,@o #,i)) (cdr (syntax->list r)))) (nest (list 'foo) (list 'bar) (list 'baz) 'qux)