How to break out from maphash in Emacs Lisp?

2019-07-15 13:59发布

问题:

I need to exit early from maphash when I've found what I was looking for.

(defun find-in-hash (str hash)
  (let ((match nil))
    (maphash (lambda (key value)
      (if (string-prefix-p str key)
        (setq match key))) hash)
    match))

How would I do this in Emacs Lisp?

回答1:

As explained in how to interrupt maphash you can place a maphash inside a block and exit the block via return-from, i.e. use the form

(block stop-mapping
  (maphash
   ;; Function to call for all entries in ht.
   ;; A condition for when to stop mapping.
     (return-from stop-mapping)
   ht))

Note that this requires cl which can be required via (require 'cl). As mentioned in a comment the same result can be achieved in pure elisp via

(catch 'stop-mapping
  (maphash
   ;; Function to call for all entries in ht.
   ;; A condition for when to stop mapping.
     (throw 'stop-mapping retval)
   ht))


回答2:

A bit of self-promotion here :)

I've been working (although not quite as much recently) on a set of macros to make it more uniform and, hopefully, easier to do all kinds of iteration on various collections available in Emacs Lisp. Here it is: https://code.google.com/p/i-iterate/ it is not 100% finished and tested, but for the most part it is.

As has already been said, the only way to break from maphash is to throw an error. But this is just something Emacs Lisp acquired at the time it was designed. Many older languages have special primitives for iterating over particular collections, or for performing numerical iteration, while they don't have a language-level abstraction of iteration. loop macro in cl package in Emacs Lisp is one (good) way to address the situation, but by its nature it has to mirror the same macro in Common Lisp, and that macro isn't extensible (you cannot add your own drivers to it, even if some implementation allows it).

The library I worked on tries to follow in spirit another Common Lisp library: iterate and borrows many ideas from there.

Just to illustrate what loop macro can do:

(loop with hash = (make-hash-table)
      initially 
      (setf (gethash 'a hash) 'b
            (gethash 'b hash) 'b
            (gethash 'c hash) 'c)      ; initialize variables 
                                       ; before any iteration happens
      for x being the hash-key in hash
      using (hash-value y)             ; define variables used in iteration
      collect (list x y) into z        ; some predefined functionality
      until (eq x y)                   ; termination condition
      finally (return (cons 'd z)))    ; returning from iteration
;; (d (a b) (b b))

With the benefit of it working similarly for hash tables, arrays or lists.

Similar code using ++ macro:

(++ (with ((hash (let ((h (make-hash-table)))
                   (setf (gethash 'a h) 'b
                         (gethash 'b h) 'b
                         (gethash 'c h) 'c) h))))
  (for (x . y) pairs hash)
  (collect (list x y) into z)
  (when (eq x y) (return (cons 'd z))))
;; (d (b b) (a b))

(I don't have anything analogous to initially yet)



标签: emacs elisp