help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Eval keymapp in a macros


From: Arthur Miller
Subject: Re: Eval keymapp in a macros
Date: Tue, 03 Aug 2021 23:20:29 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux)

Michael Heerdegen <michael_heerdegen@web.de> writes:

> Arthur Miller <arthur.miller@live.com> writes:
>
>> #+begin_src emacs-lisp
>>
>> (defmacro with-key-map (mapname &rest body)
>>   `(let ((map ,mapname))
>>      (dolist (def '(,@body))
>>         (define-key map
>>       (if (vectorp (car def)) (car def)
>>         (read-kbd-macro (car def)))
>>       (if (or (listp (cdr def))
>>               (functionp (cdr def)))
>>           (cdr def)
>>         (if (eval `(keymapp ,(cdr def)))
>>             (eval (cdr def))))))))
>>
>> #+end_src
>
> Unfortunately you didn't show how you use it.  What's the purpose of
> your macro? 

Indeed, I didn't, I appologize.

>                                                What's the purpose of
> your macro?

It is just a simple wrapper for a define-key so I save some
typing. I have actually rewrote it to not use cons any more but read
ordinary list by pairwise elements, but that does not matter, the real
work done is still the same.

Here is example from my init file (with conses):

             (with-key-map global
                           ;; Window-buffer operations
                           ("C-<insert>"    . term-toggle)
                           ("<insert>"      . term-toggle-eshell)
                           ([f9]            . ispell-word)
                           ([S-f10]         . next-buffer)
                           ([f10]           . previous-buffer)
                           ([f12]           . kill-buffer-but-not-some)
                           ([M-f12]         . kill-buffer-other-window)
                           ([C-M-f12]       . only-current-buffer))
                           
My original macro didn't handle the case when an indirect keymap should
be passed to define-key, someone on reddit point it out with example below:

(setq pkg-ops-map
      (let ((map (make-sparse-keymap "Packages")))
        (with-key-map map
                      ("h" . '("describe" . describe-package))
                      ("a" . '("autoremove" . package-autoremove))
                      ("d" . '("delete" . package-delete))
                      ("i" . '("install" . package-install))
                      ("s" . '("selected" . package-install-selected-packages))
                      ("r" . '("refresh" . package-refresh-contents))
                      ("l" . '("list" . list-packages)))
        map))

(with-key-map global-map ("C-c p" . pkg-ops-map))

So I rewrote it like this:

(defmacro with-key-map (mapname &rest body)
  `(dolist (def '(,@body))
     (define-key ,mapname
       (if (vectorp (car def)) (car def)
         (read-kbd-macro (car def)))
       (if (keymapp (cdr def))
           (eval (cdr def))
           (cdr def)))))

But I get error: Wrong type argument: commandp, pkg-ops-map
       
So I ended with the one I posted which works, but I wonder why, and I
don't really like it. I think you are correct about the reason. That is
what I think also, so that is why I used eval, because I need the keymap
object itself to pass to define-key, at least so I think. Maybe I am
wrong there? 

>              Anyway, `macroexpand' or `macroexpand-1' your call to see
> what happens.

I know. I am aware of macroexpand, I do use it. The above macro does
expand to what I wanted, or better to say to what I thought I wanted,
but it seems that it is not correct. Most correctly, it does not expand
so much, since dolist itself is a macro:

(insert (pp (macroexpand-1 
'(defmacro with-key-map (mapname &rest body)
  `(dolist (def '(,@body))
     (define-key ,mapname
       (if (vectorp (car def)) (car def)
         (read-kbd-macro (car def)))
       (if (keymapp (cdr def))
           (eval (cdr def))
       (cdr def)))))
)))

(defalias 'with-key-map
  (cons 'macro
        #'(lambda
            (mapname &rest body)
            `(dolist (def '(,@body))
               (define-key ,mapname
                 (if (vectorp
                      (car def))
                     (car def)
                   (read-kbd-macro (car def)))
                 (if (keymapp (cdr def))
                     (eval (cdr def))
                   (cdr def)))))))

I have edited spaces for readability.

> Note that nothing in BODY is ever evaluated (it's behind a quote).

Stuff in dolist gets evaluated, because dolist itself is a macro.
The real body of function which is all in do list gets expanded by
dolist (or the real interested part by 'while' which seems to be a
special form) and then evaled by dolist. So dolist will actually call
define-key for me, and that is what seem to happend, because stuff gets
defined after I call the macro. I hope I understand correctly what is
going on there.

Also I have to test for listp and functionp in correct order. When I
switch orders then I get errors too, so I am not so happy about that
macro att all.

As a side note, this isn't really a macro that writes a function or
another macro and returns it. I have it partly to save myself typing,
and partly to skip overhead of macroexpansion when Emacs start. Byte
compiler will expand it when init file is byte compiled. Actually I
wrote a program to write my init file which does expansion when it outputs
code to init file but it is just another regression. Anyway these are
just side notes, the macro works or should work in interpretter too.

> My tip when writing a macro (do you really need one btw?): Write an
> example call and then the desired expansion down.  Only after that write
> down the macro implementation that offers exactly that expansion.  You
> can later do that in your head, but if you skip that step you get all
> the surprises and pitfalls that macros are known for.

Indeed, I completely agree.

Thank you for the answer and for trying to help me. I am really confused
what happends there, so I really appreciate if you (or anyone) can make
it a bit more clear.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]