Search code examples
lispelisp

How to use functions inside an alist?


I used a function(concat) inside an alist, as follows:

(setq org-dir-to-image-dir-alist
  '(((concat "a" "-" "b") . "c")))

(dolist (cell org-dir-to-image-dir-alist)
  (print (car cell)))

The result is:

(concat "a" "-" "b")

That is not what I want. The concat function doesn't seem to be evaluated. Why? And how to make it evaluated(i.e. output a-b)?


Solution

  • Use list to Create a Fresh List

    '(((concat "a" "-" "b") . "c")) is a list literal: quoting a list structure means that no part of that list structure is evaluated. Instead, use list and cons to cons up fresh data structures in the usual way:

    (list (cons (concat "a" "-" "b") "c"))
    

    Both list and cons are functions which evaluate their arguments. Here, (cons (concat "a" "-" "b") "c") creates the pair ("a-b" . "c"), and calling list on the result of the last expression places it in a fresh list: (("a-b" . "c")).

    An experiment in the REPL might help clarify things. Given:

    (setq x 42)
    

    when you evaluate x in the REPL, the result is 42. Evaluation of the list literal '(x) yields the list (x), which is probably not what was desired. But, evaluation of (list x) yields the list (42).

    Note also that list literals cannot be mutated, i.e., you cannot apply destructive operations such as setcar or nconc to list literals. That is to say, programs which attempt to modify quoted lists have behavior which is undefined. This undefined behavior includes appearing to work correctly, until one day something goes horribly wrong. See 2.9 Mutatability and 5.6 Modifying Existing List Structure. But lists created via list or cons can be safely mutated, if the need arises.

    Using the Backquote Syntax

    It is also possible to use the backquote syntax here, but I do not recommend it. Backquoting, or quasiquotation, provides a means to create a list template with control over evaluation. Using a backquote by itself in front of a list, instead of the usual quote character, is equivalent to quoting the list. But, you can add auxiliary characters for finer control over the results; placing a comma in front of a list element means that the element will be evaluated before the list is created. So, you can do this:

    `((,(concat "a" "-" "b") . "c"))
    

    Here the expression (concat "a" "-" "b") is evaluated before the rest of the list template is converted to a list. And, there are a bunch of other things that you can do in a quasiquote form, too. For example, you can splice the list result of evaluating some form into the surrounding list in the quasiquote form.

    Pretty neat. So, why don't I recommend this solution? First, there is no gain in code clarity by using this, in fact it might seem less clear to many; the evaluation model in a quasiquote form is not the same as that in a regular function call, and not the same as that in a quote form. The version using list and cons is crystal clear, and the evaluations are easily understood.

    Second, quasiquotation can be subtle and complex; it is easy to get bugs in this kind of code, and it is even possible to wander into undefined behavior.

    Third, and this is probably really an extension of the second point, the list and cons version is easier to maintain. Suppose that the quoted expression is changed so that a variable x is introduced in place of "c". The maintainer of the quasiquote version will need to be vigilant:

    `((,(concat "a" "-" "b") . x))
    

    will not work as expected; the maintainer will need to remember to add a comma so that x is evaluated to its value before the list is created:

    `((,(concat "a" "-" "b") . ,x))
    

    By using the list and cons version, changing the literal "c" to the variable x will just work:

    (list (cons (concat "a" "-" "b") x))
    

    Generally speaking, quasiquotation is great for writing macros, and it can be handy for more complex list expressions; proliferating list expressions can get very messy (and that is exactly what happens when macros are attempted without quasiquotation). For simple problems like the one here, using quasiquotation really isn't worth the trade-offs.