- Terse Hashes in Emacs Lisp
- Data::Dumper in Emacs Lisp
- Terse Hash Dereferencing
- Terse Hashes Miscellany
One thing I really love about Perl is the easy and natural syntax for deeply nested data structures. I can make a hash of lists of hashes without a second thought.
(Okay, okay, Ruby and Python are equally good or even slightly better here, but I’m thinking about in comparison with emacs lisp, and C++ and Java…)
my $data = { list1 => [ { x => 'y' }, { a => 'b' }, # etc ... ], # etc ... }
In contrast, the syntax for dealing with hashes in emacs lisp is extremely verbose. Not only that, but for me, the arguments are the wrong way round. In object based programming, the object is always the first argument.
(make-hash-table) (puthash <key> <value> <hash-table>)
When I came across the Clojure syntax for hash sets using #{ ... } I thought what a good idea. Unfortunately, emacs lisp doesn’t have reader macros. Let’s see what happens when I try it with a function.
(defun hash-set (opening-brace &rest args) nil) (hash-set { 1 2 3 4 })
The result tells us that emacs is trying to evaluate the value of { as a variable before passing it to the function.
Debugger entered--Lisp error: (void-variable {)
(hash-set { 1 2 3 4 })
eval((hash-set { 1 2 3 4 }))
...
Fortunately, in emacs lisp, we can delay the evaluation of the arguments using a macro. I use the same macro to make setting and retrieving individual hash values easy too.
(defun literal-hash (&rest args) (let ((hash (make-hash-table))) (while args (let* ((key (pop args)) (val (pop args))) (puthash key val hash))) hash)) (defmacro _h (arg1 arg2 &rest args) (cond ((eq arg1 '{) ;; check for empty case (if (eq arg2 '}) (make-hash-table) ;; (_h { k1 v1 k2 v2 ... }) (let ((rest (cons arg2 args)) elem tmp) (while (not (null rest)) (setq elem (pop rest)) (when (not (or (eq elem '}) (eq elem '=>))) (push elem tmp))) (setq tmp (reverse tmp)) `(literal-hash ,@tmp)))) ((null args) `(gethash ,arg2 ,arg1)) (t (let ((val (car args))) `(puthash ,arg2 ,val ,arg1)))))
Even nested hashes work nicely (I might be betraying my perl biases here!)
(defvar %hash (_h { 1 2 3 (_h { 1 2 3 4 } ))) (_h (_h %hash 3) 1) ;; ==> 2
Okay, retrieval is a little awkward, but I could add some more syntax to _h to make the following work.
(_h %hash -> 3 1)
That is left as an exercise for the reader.
Update: Hi atomicrabbit, I couldn’t figure out how to do the syntax highlighting in the comment so I have included my reply below.
I didn’t know about the #s(make-hash ...), so that looks pretty cool. I found a mention in the Elisp Cookbook. I couldn’t get it to work though unfortunately. Any ideas?
(emacs-version)
;; "GNU Emacs 23.0.60.1 (i386-mingw-nt6.0.6000)
;; of 2009-01-12 on LENNART-69DE564 (patched)"
(read (prin1-to-string (make-hash-table)))
(read "#s(hash-table data ('key1 'val1 'key2 'val2))")
Debugger entered--Lisp error: (invalid-read-syntax "#") read("#<hash-table 'eql nil 0/65 0x143c880>")
Debugger entered--Lisp error: (invalid-read-syntax "#") read("#s(hash-table data ('key1 'val1 'key2 'val2))")
This should work in Emacs 23:
#s(hash-table data (“key1″ “val1″ “key2″ “val2″))
To get tighter indentation when using the macro, I’d throw this in the _h definition,
(declare (indent defun))
Also, the #s syntax is specifically 23.2, so 23.0 won’t cut it,
http://www.gnu.org/software/emacs/NEWS.23.2
Hi Chris,
Thanks for the (declare …) tip.
And good to know that #s is for 23.2 onwards. I wasn’t sure what I was doing wrong. It must be time to upgrade again.