Foognostic blogs Seeking knowledge of foo

10Apr/09Off

Configuring projects in org mode, or defining variables in Lisp is strange

Org mode in GNU Emacs makes it somewhat easy to publish its files as a small static website. You define the files to publish, how to convert them, where to publish them, and so on. The bad news is that you need to manually write a big data structure in Emacs Lisp. Not what a Lisp newbie really wanted to hear (no customize page?) but I decided org mode was worth the pain. Speaking of pain, here's the data structure:

 0:  (set
 1:    (quote org-publish-project-alist)
 2:    '
 3:    (
 4:      (
 5:        "tsn"
 6:        :base-directory "~/Documents/foog/tsn"
 7:        :base-extension "org"
 8:         :publishing-directory "/dev/null"
 9:         :publishing-function org-publish-org-to-html
10:      )
11:    )
12:  )

All that work just to define a list of lists? I could just write it in Ruby as:

 # pretend this function exists in Ruby
 # def org_publish_to_html() end
org_publish_project_alist = [
    [ "tsn",
      :base_directory, "~/Documents/foog/tsn",
      :base_extension, "org",
      :publishing_directory, "~/Documents/foog/tsn_export",
      :publishing_function, org_publish_to_html ]
];

The major difference is all this set and quote nonsense. How many function calls does it take to initialize a variable in Lisp?? It turned out that answering that question well required some spelunking into Lisp. It kind of makes me appreciate GNU Emacs Lisp as a low-level high-level language.

And now, we plunge through the rabbit hole!

So, why call the (quote) function when defining a variable? I didn't realize what a good question this was. The answer means paying close attention to references and values. Let's take a quick look at a function which determines whether its argument is a value or a reference:

(symbolp 123)           => nil  ;; nil is false in Emacs Lisp
(symbolp (quote 123))   => nil  ;; 123 passes through...
(symbolp (quote "abc")) => nil  ;; abc passes through
(symbolp (quote foo))   => t    ;; but foo is a symbol.

Okay, so now we can distinguish between a value and a reference. The next step is assigning a value to a reference. This is where (quote) is essential.

(set foo 123) => Lisp error: (void-variable foo)

That silly Lisp interpreter, trying to get a value while we are in the middle of setting it. That's where (quote) comes in. The function call to quote is evaluated, and the result is passed as the first argument to (set). That makes some sense, but because variable definition happens ALL THE TIME in imperative programming languages Emacs Lisp has tried to make this as easy as possible:

(set (quote foo) 123)  =>; 123  ;; No one does this
(set 'foo 456)         =>; 456  ;; 'foo = (quote foo)
(setq foo 789)         =>; 789  ;; People do this one.

INTERMISSION

Now that variable assignment has been hashed out we can pick up speed a bit and finish this off:

  • Line 1 specifies the symbol we want to use.
  • Line 2 is just a macro for (quote), so the contents will be returned without being evaluated.
  • Line 3 starts a list. To me this implies that the variable org-publish-project-alist will contain an array of ... well, not sure yet.
  • Line 4 starts a list. This list will be the first item in the list defined by org-publish-project-alist.
  • Line 5 is just a string. It is the first element in the list. Calling (car) on the list started at line 4 would return "tsn".
  • Line 6 contains the 2nd and 3rd items in the list. :base-directory is a keyword symbol ((symbolp :base-directory) => t). "~/Documents/foog/tsn" is the next element in the list.
  • Lines 7-9 define the remnant of the list started at line 4.
  • The remaining lines are the obligatory closing parens.

Probably because I am very new to Lisp I don't understand why flat lists are used to store this data. Key value pairs are a great fit for hash maps or associative lists (the variable name ends with alist...). My first attempt to restructure the data would be:

(setq org-projects
  '(
     ("tsn"
       (:base-directory . "~/Documents/foog/tsn")
       (:base-extension . "org")
       (:publishing-directory . "~/Documents/foog/tsn_export")
       (:publishing-function . org-publish-org-to-html))
     ("css"
       (:base-directory . "~/Documents/foog/tsn/css")
       (:base-extension . "css"))
     ("img"
       (:base-directory . "~/Documents/foog/tsn/images")
       (:base-extension . "jpg|png"))
  ))

The difference being that key value pairs are stored in an associative list. This lets you take advantage of built in functions for getting property values.

(mapcar
  (lambda (elt)
    (let
      ((project (car elt))
       (properties (cdr elt)))
      (print (format "Project %s has base extension %s"
                      project
                      (cdr (assoc :base-extension properties))))))
  org-projects)

Output: ("Project tsn has base extension org" "Project css has base extension css" "Project img has base extension jpg|png")

Wow, look at all the rambling. At least now I understand how to manually configure org mode's projects.

Lisp is neat.

Comments (3) Trackbacks (0)
  1. A flat list with keyword-value pairs is called a property list, or plist for short. As with associative lists, there are functions for manipulating them built in. I don’t know why a plist is used for this specific instance, but it’s probably a combination of historical reasons and being simpler to type.

    Any particular reason you’re using nonstandard indentation? It’s almost never a good idea to use lines exclusively for opening or closing parentheses – yeah, lots of other languages do it, and I’m sure it’s habitual for lots of programmers, but it’s unneeded in Lisp and generally only serves to add visual complexity. For the most part, you should be relying on indentation to read where things begin and end.

    That said, it’s good to see someone delving into Lisp and learning why it does things the way it does, rather than dismissing it when it happens to behave unexpectedly. :)

  2. Alice,

    I was pretty sure I was missing something about the flat list. I noticed property lists in the Elisp documentation but apparently need to pay more attention. :-)

    The nonstandard indentation comes mostly from having written more Lisp than read it. My real world peers are fantastic but shy away from Lisp. So if I’m going to learn it I’m just going to have to write some. Pretty soon I’ll post some Elisp to generate the sieve of Eratosthenes; this was my revelation that tail calls in Elisp chew the stack away.

    The other reason for the pedantic indentation was to be explicitly clear about where the expressions are. It helped me quite a bit and I like Lisp. The hope was for any fencesitters to take a closer look, compare and contrast with the Ruby code, and maybe hopefully warm to Lisp a little.

    I appreciate the helpful, insightful, and civil comment!

  3. Here’s what you’d want to access a property of a specific project in that list:

    (plist-get (cdr (assoc “tsn” org-publish-project-alist)) :base-directory)

    So not significantly harder than your example, but with one list per project instead of five.


Trackbacks are disabled.

Page optimized by WP Minify WordPress Plugin

Foognostic blogs is Stephen Fry proof thanks to caching by WP Super Cache