2015-08-01 case-intern trick

When coding in Elisp, sometimes it happens that you want to branch depending on the value of some variable, which can hold a few possible values. The C way would be to use an enum; the lispy way is to use symbols and case. However, sometimes you have strings and not symbols. If you can arrange things so that you have symbols, it’s fine; sometimes you can’t, though, especially if you use someone else’s code. For instance, when writing an Org-mode exporter, you might want to dispatch on link types; however, (org-element-property :type link) is a string (one of a few enumerated in the org-link-types variable).

Of course, you can just use cond:

 ((string= (org-element-property :type link) "http") (do-something))
 ((string= (org-element-property :type link) "file") (do-something-else))
 (t (do-something-else-yet)))

This is far from elegant, though.

Here is one possible solution. While Elisp doesn’t have a case form, cl (the package implementing many Common Lisp features) has cl-case (conveniently aliased to case). However, it uses eql to compare keys. Since (eql "foo" "foo") evaluates to nil (because these two strings are different objects in memory), case‘ing on strings doesn’t make sense.

The solution is to use intern. Basically, it may be considered as a “type cast from strings to symbols”. (This is just a bit more complicated, but never mind.) For instance, (eql (intern "foo") (intern "foo")) evaluates to t, as expected. This means that the above construct may be rewritten much more elegantly as

(case (intern (org-element-property :type link))
  (http (do-something))
  (file (do-something-else))
  (t (do-something-else-yet)))

Worth remembering.

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode