Feeds:
Posts
Comments

Posts Tagged ‘terse hashes’

Terse Hashes Miscellany

This is part 4 in my terse hashes in emacs lisp series

Okay, time to wrap up with the terse hashes.

The built-in hash tables have equivalents to the perl exists and delete, but again, the argument order does not feel right to me.

Having looked at ruby a bit over the past few months, I’ve decided I like predicates ending with a question mark1 rather than a p.

(defsubst exists? (hash key)
  (not (eq (gethash key hash 'missing-key) 'missing-key)))

(defsubst del-hash! (hash key) (remhash key hash))

And the other thing I rely on a lot in perl is keys so I can choose the traversal order with sort.

(defun keys (hash)
  (let (keys)
    (maphash (lambda (k v) (push k keys)) hash)
    keys))

I need a decent less than method

(defun generic/less (val1 val2)
  (if (and (numberp val1) (numberp val2))
      (< val1 val2)
    (let ((v1 (prin1-to-string val1))
          (v2 (prin1-to-string val2)))
      (string< v1 v2))))

and then this sort of thing just works

(defvar %hash (_h { 1 2 'a 'x 5 6 10 "lemon" }))
(sort (keys %hash) #'generic/less) ;; --> (1 5 10 a)

1. Was this first in scheme?

Read Full Post »

This is part 3 in my terse hashes in emacs lisp series

Since emacs 23.2 was released the hash creation part of my _h macro is now obsolete. (or maybe not – perhaps allowing nested braces would make usage cleaner at the cost of significant complexity)? However, I suspect that is only one part of what would make emacs lisp nested data structures pleasant to use.

The other piece of the puzzle is dereferencing.

We don’t need a macro to implement the set and get functionality that _h includes so far. It was only required to prevent the braces from being evaluated.

Previously I mentioned extending the _h macro to allow for easier, or at least terser, nested hash dereferencing. The syntax I suggested would still need a macro. And I might as well leave the braces hash creation. It is unlikely my firm will upgrade to 23.2 in the next few months.

Given this nested %hash

(defvar %hash)
(setq %hash (_h { 1 2
                  'h (_h { 3 4
                           5 6
                           'h2 (_h { 1 2
                                     3 [5 6 7]
                                     'vh (vector (_h { 1 2 })
                                                 (_h { 3 4 }))
                                   })
                         })
                }))

I want (_h %hash -> 'h 'h2 'vh [1] 3) to return 4.

(It might be nice to add a nested assignment such as

(_h %hash -> 'h 'h2 'vh [1] 3 := "hello")

but the dereference will do for now.)

(defsubst h/vref (num)
  (cons 'vecpos num))

(defmacro _h (arg1 arg2 &rest args)
  (declare (indent defun))
  (cond ((eq arg1 '{) ...)
        ((eq arg2 '->)
         (let (str new-args)
           (dolist (elem args)
             (setq str (prin1-to-string elem))
             (if (string-match "\\[\\([0-9]+\\)\\]" str)
                 (let ((pair (h/vref (string-to-number
                                      (match-string 1 str)))))
                   (push (cons 'quote (list pair)) new-args))
               (push elem new-args)))
           (setq new-args (reverse new-args))
           `(h/get-value (quote, arg1) ,arg1 ,@new-args)))
        ((null args) ...)
        (t ...)))

That might be a little over the top, but it demonstrates that the macro facility does pretty much have access to the full power of the language.

The new functionality relies on a helper function, h/get-value.

(defun h/get-value (var-name variable &rest args)
  (let (retval param previous-param)
    (when (<= (length args) 0)
      (error "(_h %s -> ...) was not passed any arguments" var-name))
    (setq retval variable)
    (while args
      (setq param (pop args))
      (cond ((and (consp param) (eq 'vecpos (car param)))
             (let ((ref (cdr param)))
               (setq param (intern (concat "[" (number-to-string ref) "]")))
               (if (vectorp retval)
                   (setq retval (aref retval ref))
                 (error "%s not a vector @ %s (%s)"
                        var-name param previous-param))))
            ((hash-table-p retval)
             (setq retval (gethash param retval 'missing-key))
             (when (eq retval 'missing-key)
               (error "Can't find nested hash value %s in %s (%s)"
                      param var-name previous-param)))
            (t (error "value %s returned for param %s is not a hash in %s"
                      retval previous-param var-name)))
      (setq previous-param param))
    retval))

(macroexpand '(_h %hash -> 'h 'h2 'vh [0]))
--> (h/get-value (quote %hash) %hash (quote h) (quote h2) (quote vh) (quote (vecpos . 0)))

The problem with extending syntax like that is what if we want to use a variable to reference the vector rather than hardcoding a value like [0]. The obvious solution doesn’t work.

(defvar test-var)
(setq test-var 1)

(defsubst vec-ref (num)
  (intern (concat "[" (number-to-string num) "]")))

(_h %hash -> 'h 'h2 'vh (vec-ref test-var))

Debugger entered--Lisp error: (error "value [#<hash-table 'eql nil 1/65 0x1438680> #<hash-table 'eql nil 1/65 0x1438200>] returned for param vh is not a hash in %hash")
  signal(error ("value [#<hash-table 'eql nil 1/65 0x1438680> #<hash-table 'eql nil 1/65 0x1438200>] returned for param vh is not a hash in %hash"))

(the error message indicates there is a problem with h/get-value too but no time to fix before we went to press)

It is easier to go down to the next level – the code that the macro generates. The following performs as required.

(_h %hash -> 'h 'h2 'vh (h/vref test-var) 3)

Hopefully I didn’t offend too many senses of aesthetics there.


For reference, the full _h

(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))))
        ((eq arg2 '->)
         (let (str new-args)
           (dolist (elem args)
             (setq str (prin1-to-string elem))
             (if (string-match "\\[\\([0-9]+\\)\\]" str)
                 (let ((pair (h/vref (string-to-number
                                      (match-string 1 str)))))
                   (push (cons 'quote (list pair)) new-args))
               (push elem new-args)))
           (setq new-args (reverse new-args))
           `(h/get-value (quote, arg1) ,arg1 ,@new-args)))
        ((null args)
         `(gethash ,arg2 ,arg1))
        (t (let ((val (car args)))
             `(puthash ,arg2 ,val ,arg1)))))

Read Full Post »

This is part 2 in my terse hashes in emacs lisp series

Another thing I like about Perl data structures, is that once I have stuffed all my data in there, I can easily see what it looks like with Data::Dumper.

my $data = {
    list1 => [
        { x => 'y' },
        { a => 'b' },
        # etc ...
    ],
    # etc ...
}

print Dumper($data);

And the result is:

$VAR1 = {
          'list1' => [
                       {
                         'x' => 'y'
                       },
                       {
                         'a' => 'b'
                       }
                     ]
        };

This is a big help with Exploratory Programming as you can see if you’re stuffing values in the right place.

You might be surprised to learn that I want the same functionality in Emacs Lisp.

(defun hash-dumper (hash &optional indent)
  (when (null indent) (setq indent 0))
  (let (lines indent-string)
    (setq indent-string "")
    (dotimes (i indent) (setq indent-string (concat " " indent-string)))
    (maphash (lambda (k v)
               (if (hash-table-p v)
                   (push (format "%s =>\n%s"
                                 k (hash-dumper v (+ indent 2))) lines)
                 (push (format "%s => %s" k v) lines)))
             hash)
    (concat (format "%s{" indent-string)
            (mapconcat #'identity lines (format "\n%s " indent-string))
            "}")))

And the result…

(defvar %hash)
(setq %hash (_h { 1 2
                  'h (_h { 3 4
                           5 6
                           'h2 (_h { 1 2
                                     3 [5 6 7]
                                     'vh (vector (_h { 1 2 })
                                                 (_h { 3 4 }))
                                   })
                         })
                }))

;; (insert (format "\n%s" (hash-dumper %hash)))
{h =>
  {h2 =>
    {vh => [#<hash-table 'eql nil 1/65 0x17cce80> #<hash-table 'eql nil 1/65 0x16f0a00>]
     3 => [5 6 7]
     1 => 2}
   5 => 6
   3 => 4}
 1 => 2}

Not bad, but not quite right. Fixing the problem with lists of hashes is left as an exercise for the reader.

Read Full Post »

This is part 1 in my terse hashes in emacs lisp series

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))")

Read Full Post »

Follow

Get every new post delivered to your Inbox.