On high level: I'm trying to create an association list in which value is a result of function execution. What I get instead is an expression which represents that function, and which needs to be wrapped into "eval" to get it to work. I'm trying to understand why, and what makes the behavior different from regular lists.
In more details:
I'm putting together a configuration for org agenda which has a common functionality for all the environments, but then I want to make it possible for a specific environment to add something extra. So, I know that on all the machines org-agenda-files need to include these two dirs: "~/Documents/Org" and "~/Downloads/Org" but I want to let a specific machine to register more dirs in addition to those two and which would only be visible to that machine.
So I build an association list in which machine name is the key, and the value is a list of dirs that need to be handled on that node in addition to those shared by all.
The code looks like this:
;; default-agenda-files are shared by all the environments
(setq default-agenda-files
'("~/Documents/Org" "~/Downloads/Org"))
;; in addition to default, I want to register project-abc
;; dirs for nodeABC and project-xyz dirs for nodeXYZ
(setq per-node-agenda-file-mappings
'(("nodeABC" . (append default-agenda-files
'("~/Projects/project-abc/doc/"
"~/Projects/project-abc/notes")))
'("nodeXYZ" . (append default-agenda-files
'("~/Projects/project-xyz/doc"
"~/Projects/project-xyz/notes")))))
The code further sets org-agenda-files depending on the machine name.
Here's the problem. If I do
(alist-get "nodeABC" per-node-agenda-file-mappings nil nil 'string-equal)
I get
(append default-agenda-files '("~/Projects/project-abc/doc/" "~/Projects/project-abc/notes"))
rather than
("~/Documents/Org" "~/Downloads/Org" "~/Projects/project-abc/doc/" "~/Projects/project-abc/notes")
I can solve it by doing
(eval (alist-get "nodeABC" per-node-agenda-file-mappings nil nil 'string-equal))
Then everything works.
But I'm trying to understand what's going on there and why same thing doesn't happen with regular lists (with regular lists, evaluation does happen at the time of assignment). Is there a way to make evaluation happen at the time of assignment in this case too? I double-checked, same thing happens if I use hash-table instead of association list.
There's nothing different about using alists from other lists. What you want here is to evaluate some things and not evaluate others. Just do that: quote only the things you don't want evaluated.
You don't need to explicitly invoke eval
- Lisp already invokes it implicitly. All you need to do is not evaluate things that you want to treat as data. Here, that means strings (those are constant anyway, so evaluating them makes no difference) and any lists that you want to be just, well, lists - e.g., '("~/Projects/project-abc/doc/" "~/Projects/project-abc/notes")
.
You want to use backquote instead of quote, and use comma before the (append...)
sexp:
(setq per-node-agenda-file-mappings
`(("nodeABC" . ,(append default-agenda-files
'("~/Projects/project-abc/doc/"
"~/Projects/project-abc/notes")))
("nodeXYZ" . ,(append default-agenda-files
'("~/Projects/project-xyz/doc"
"~/Projects/project-xyz/notes")))))
Or this:
(setq per-node-agenda-file-mappings
`(("nodeABC" ,@(append default-agenda-files
'("~/Projects/project-abc/doc/"
"~/Projects/project-abc/notes")))
("nodeXYZ" ,@(append default-agenda-files
'("~/Projects/project-xyz/doc"
"~/Projects/project-xyz/notes")))))
Or this:
(setq per-node-agenda-file-mappings
(list (cons "nodeABC" (append default-agenda-files
'("~/Projects/project-abc/doc/"
"~/Projects/project-abc/notes")))
(cons "nodeXYZ" (append default-agenda-files
'("~/Projects/project-xyz/doc"
"~/Projects/project-xyz/notes")))))
Or just this, since the only thing you need to evaluate is variable default-agenda-files
:
(setq per-node-agenda-file-mappings
`(("nodeABC" ,@default-agenda-files
"~/Projects/project-abc/doc/" "~/Projects/project-abc/notes")
("nodeXYZ" ,@default-agenda-files
"~/Projects/project-xyz/doc" "~/Projects/project-xyz/notes")))
See the Elisp manual, node Backquote.