3

I am still in the process of understanding macros and though I think I understand the basics of "backquote" "unquote" and "unquote splicing", I had thought they were only used/useful in macros.

however I came across this Common Lisp code from Rosetta code (the calender task),

(defun month-strings (year month)
  "Collect all of the strings that make up a calendar for a given
    MONTH and YEAR."
  `(,(date-calc:center (month-to-word month) (length *day-row*))
    ,*day-row*
     ;; We can assume that a month calendar will always fit into a 7 by 6 block
     ;; of values. This makes it easy to format the resulting strings.
      ,@ (let ((days (make-array (* 7 6) :initial-element nil)))
           (loop :for i :from (date-calc:day-of-week year month 1)
                 :for day :from 1 :to (date-calc:days-in-month year month)
                :do (setf (aref days i) day))
           (loop :for i :from 0 :to 5
             :collect
             (format nil "~{~:[  ~;~2,d~]~^ ~}"
                 (loop :for day :across (subseq days (* i 7) (+ 7 (* i 7)))
                  :append (if day (list day day) (list day))))))))

here back-quote, unquote, and unquote splicing are used in a normal function, and it works to create a list of strings.

and while I do not use Scheme, the Racket solution had something similar,

(define days
    (let ([? (if (= mn 12) (λ(x y) y) (λ(x y) x))])
      (round (/ (- (find-seconds 0 0 12 1 (? (+ 1 mn) 1) (? yr (+ 1 yr))) s)
                60 60 24))))
  (list* (~a mname #:width 20 #:align 'center) "Su Mo Tu We Th Fr Sa"
         (map string-join
              (nsplit 7 `(,@(make-list pfx "  ")
                          ,@(for/list ([d days])
                              (~a (+ d 1) #:width 2 #:align 'right))
                          ,@(make-list (- 42 pfx days) "  ")))))))

which I did not test.

my questions are,

Why would this be necessary in a function, what is the use case?

and how is it different from a macro ?

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Kyuvi
  • 360
  • 3
  • 13
  • 2
    Backquotes are syntactic sugar for making s-expressions (nested list based structures). Lisp source code is made of s-expressions so backquote is useful for macros (functions whose input and output is code, which takes the form of s-expressions). Much Lisp data is made of s-expressions therefore backquote is useful for functions which manipulate data (when that data is in the form of s-expressions) – Dan Robertson Feb 04 '18 at 22:09

3 Answers3

6

quasiquote, unquote and unquote-splicing are just syntax sugar for a combination of quoted datum, list, and cons. Imagine this:

`(,a b c)    ; == (cons a '(b c))
`(a b ,c)    ; == (list 'a 'b c)
`(a b ,@c d) ; == (cons 'a (cons 'b (append c '(d))))  

These are small trivial examples, so you can imagine the right hand side might become crazy complex, but it is nice to know that the quasiquote magic makes new cons when needed and keep literals as is in the tails. Thus using nconc of quasiquoted expression would not work in the first case, but in the second and third because the last cons is needed to be fresh in those cases.

If you have a function that creates a list structure, quasiquote will make the code more clear and consice since the form will look more like the result. It is no different than a macro since both create list structure. A macro differs in what happens with the result. In a function the value is returned and in a macro code gets replaced.

You can check what happens after using the macro with macroexpand:

(macroexpand '`(,a ,b ,@c))
; ==> (cons a (cons b c)) 
; ==> t
pasja
  • 365
  • 4
  • 10
Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • The `macroexpand` stuff does not really work or isn't generally useful. Since the representation of the backquote form is undefined, it is not defined that it should be read into a macro form. – Rainer Joswig Feb 05 '18 at 11:52
  • 'just syntax sugar' -> conceptually, but not literally. – Rainer Joswig Feb 05 '18 at 11:53
  • @RainerJoswig In the same way as `list` is an abstraction for nested `cons` :-) ``'`(,a b c)`` might turn into `(list a 'b 'c)` directly, done in the reader. Perfectly OK and then the `macroexpand` would return `nil` since it hasn't done anything, but you still have a form that when evaluated would produce a list with at least one new cons. It's the same if I ask 3 lispers to produce the result without using quasiquote. Some might not expand to using CL forms but I have yet to find an implementation that doesn't – Sylwester Feb 05 '18 at 16:04
  • it allows implementations to use specific internal constructs, so that there is a difference between for example `(list a)` and `(system:bq-list a)`. Some use functions (which you can't macroexpand to Lisp primitives), others use macros internally. It then for example allows printing it as a backquote expression. Additionally the backquote expression may be read to a more complex thing, where for example the comma operator is read as a structure. Thus macroexpand would return a list with 'opaque' structures in there... – Rainer Joswig Feb 05 '18 at 16:10
  • @RainerJoswig Yeah but are you saying `system:bq-list` might not be a macro but a special function that the system has different display rules for? Seems very icky. Anyway in this case it wouldn't be much help unless you look up `system:bq-list` or read the source. Many of the implementations I use end up with forms in the `CL` namespace even if that is not a requirement. – Sylwester Feb 05 '18 at 16:15
  • The Lisp system usually also has special printing rules for a macro variant. See CLISP. That's the reason why the representation is unspecified - implementations are allowed to print backquote structures as such. If the reader would reduce them to primitive cons/list/... expressions, then the printer would not know that it was a backquote expression. That's also the reason why you can't portably traverse backquote expressions - the comma operator in some implementation for example is read as a structure.. – Rainer Joswig Feb 05 '18 at 16:18
  • @RainerJoswig IMplementation is not required to display the form with backquote. In CLISP ``'`(,a b c)`` turns into `(system:backquote (system:unquote a) b c)` and `macroexpand` returns `(cons a '(b c))` with `cons` being from the `CL` namespace. The `system:backquote` is implementation defined and the transformation may vary but `(cons a '(b c))` is pretty much very vanilla. Unlike Scheme which defined what the reader should expand to CL has more options, but by filtering it through `macroexpand` much of it is gone. – Sylwester Feb 05 '18 at 16:27
  • My CLISP 2.49 turns `'(system::backquote ((system::unquote a) b c))` into the backquote expression. Macroexpand works in CLISP, where it is a macro. Other implementations use different things. – Rainer Joswig Feb 05 '18 at 16:33
  • @RainerJoswig Yes, but my point was that `(macroexpand `expression)` will in most cases end up being something familiar in most implementations. – Sylwester Feb 05 '18 at 18:13
  • In most implementations MACROEXPAND will be a no-op (-> LispWorks, CCL, ABCL, GCL, ...), because most implementations I've seen don't read backquote expressions into a macro form. Those who do are for example CLISP and ECL. – Rainer Joswig Feb 05 '18 at 18:19
  • @RainerJoswig GCLdoes it read-time and thus you already have `(list* a '(b c))` after evaluating ``'`(,a b c)`` and no macroexpansion needed and thus the macroexpansion is a noop as anticipated. – Sylwester Feb 05 '18 at 21:02
4

Backquote

See the CLHS on Backquote.

The example

The example is similar to this code:

CL-USER 14 > (let ((a 1)
                   (b 2)
                   (c '(3 4 5)))
               `(,a                ; a gets evaluated and put in
                 ,b                ; b gets evaluated and put in
                 ,@c))             ; c gets evaluated and spliced in
(1 2 3 4 5)

The effect of above code is similar to using the function list*.

CL-USER 15 > (let ((a 1)
                   (b 2)
                   (c '(3 4 5)))
               (list* a b c))     
(1 2 3 4 5)

Which version you use is mostly a matter of taste.

list* creates a list of the first values and puts them in front of the last value, which usefully should be a list.

List creation

There are many ways and styles to create a list. The two here:

  1. use nested functions like cons, list, list*, append, ... This is especially useful when there are many elements to be computed.
  2. use templates of lists with the backquote operator and ,, ,@, ,. for evaluating. This is especially useful when there are nested lists with fixed objects and a few objects to compute.

Thus the backquoted way is useful when you think of list templates to fill out. Whenever you want to create (nested) lists, which are based on templates with a lost of constant structure (meaning objects and nesting), then this is a way to do it. This is not limited to macros - it is a general mechanism to build lists.

You can also think of templates being an inversion:

  1. functions evaluate by default and constant elements need to be quoted
  2. backquote templates don't evaluate by default and variable elements need to be unquoted.

Warning

Backquoted expressions themselves don't need to be pure lists. The internal representation of backquoted expressions is undefined and implementations actually differ.

Vectors, too

Note that this works for vectors, too:

CL-USER 20 > (let ((a 1)
                   (b 2)
                   (c '(3 4 5)))
               `#(,a
                  ,b
                  ,@c))
#(1 2 3 4 5)
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
2

Quasi-Quote (QQ) is a list constructor in Scheme.

It’s more flexible than quote ('), list or cons because it allows for mixing symbols with expression evaluations.

The QQ has two helper mechanisms:

  1. unquote - denoted (,)

  2. unquote-splicing - denoted (,@)

When QQ is used, a quote context is initiated. Unquote allows us to momentarily escape the quote context and evaluate the expression right after the unquote. Unquote-splicing both escapes an entire list from the quote context as well as “unwraps” the list. Consider:

(define b 5)
(define s (list 1 2))

Note the differences in the values of the following expressions: Quote, Quasi-Quote.

Input:

'(a b c)
`(a b c)

Output

> (a b c)    
> (a b c)

Input

'(a ,b c)    
`(a ,b c)

Output

> (a ,b c)   
> (a 5 c)

Input

'(a ,s c)    
`(a ,s c)

Output

> (a ,s c)   
> (a (1 2) c)

Input

'(a ,@s c)   
`(a ,@s c)

Output

> (a ,@s c)  
> (a 1 2 c)

Source: Compilation course I took , The Common Lisp Cookbook - Macros and Backquote

Eggcellentos
  • 1,570
  • 1
  • 18
  • 25
  • 1
    Worth noting that, while the question references scheme and these examples work in scheme, the question also references Common Lisp where these do not work as it is illegal to use a comma outside of a backquote. – Dan Robertson Feb 04 '18 at 22:12
  • I’m not really sure where this is coming from. 1) I’m not really anonymous—this is my real name. 2) in scheme if you enter `'(a ,b)` you get `(a ,b)`, syntactic sugar for `(a (unquote b))`. If I try the same in (sbcl) Common Lisp I get an error: `Comma not inside backquote`, a condition of type `SB-INT:SIMPLE-READER-ERROR`. – Dan Robertson Feb 07 '18 at 16:12
  • I would say it to a stranger or coworker. I don’t think the phrase is a slam or aggressive. For what it’s worth if the question were just about scheme then this would be a perfectly good example. The differences between scheme and Common Lisp in this regard are small and subtle not really warrant a new answer to a question not about the differences between the languages – Dan Robertson Feb 07 '18 at 16:23