unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
From: "Pascal J. Bourguignon" <pjb@informatimago.com>
To: help-gnu-emacs@gnu.org
Subject: Re: How to write the "interactive" form for a command acting on a region
Date: Wed, 14 Jan 2015 03:04:41 +0100	[thread overview]
Message-ID: <87lhl6w212.fsf@kuiper.lan.informatimago.com> (raw)
In-Reply-To: mailman.17861.1421190402.1147.help-gnu-emacs@gnu.org

Marcin Borkowski <mbork@wmi.amu.edu.pl> writes:

> On 2015-01-13, at 23:38, Pascal J. Bourguignon <pjb@informatimago.com> wrote:
>
>> If you want a different behavior, then you should have different
>> functions:
>
> Why?  In many Emacs functions/commands it works like what I want to
> have.  What's wrong with this approach?  And in fact, I /don't/ want
> different behavior: I want both the function and the command to
> (essentially) do the same, with the (minor) difference that the function
> will return a value and the command will print a message.

Indeed, the question is how you define _different_ behavior, and whether
you really have a different behavior or not.  

Usually you need just to be able to run the function interactively
easily. (You can always use M-: to call a function interactively, eg.:

        M-: (my-function (point-min) (point-max)) RET

if you don't have a command to do that.


 
>> Therefore interactive "r" will always give you start and end points.
>> You could have a command such as:
>>
>>     (defun my-command (start end)
>>        (interactive "r")
>>        (if (use-region-p) ; region is active
>>           (my-function start end)
>>           (my-function (point-min) (point-max))))
>
> This does not seem very lispy to me, though most probably have much less
> experience than you...

Notice how short this command is (it's always good to write short
functions and commands).

But more importantly, I feel that emacs lisp code would benefit in using
a more abstracted and layered approach.

The requirements of a functional API are not the same as of a user
interface command set.  

This is why you could want to define a set of data structures and
functions,  and later add a command layer calling those functions, with
variants such as in this my-command example.  We could assume that
my-function could be used also by other functions or other commands.

In any case, if the code of my-function was put inside my-command,
instead of being factored out, it would become much harder to use
my-command from other functions or commands (perhaps those other
commands or functions really want to work on the whole buffer even when
there's an active region).

When you see a command in emacs that has hundreds of lines of code (and
where the best "abstraction" they can do is:

    (defun bad-command (…)
       (interactive …)
       … hundreds of lines …
       (bad-command-1 …))

    (defun bad-command-1 (…)
       … hundreds of lines …
       (bad-command-2 …))

    (defun bad-command-2 (…)
       … hundreds of lines …)

and there are more than one such bad examples :-(, well, this is not
good at all.


>> Otherwise, if the behavior of your command and your function was the
>> same, you could write a single command, using (require 'cl) to deal with
>> the default values.  
>
> I'll have to check cl (I use it anyway for (incf)), but again: what's
> wrong with (or start (point-min))?

Nothing wrong.  Just that (interactive "r") will never pass nil for
start or end.



Notice that you can also just use (interactive) and (region-beginning) and
(region-end) inside the command.  But then one might worry about
commands that don't declare with interactive the parameters they use and
require from the user. If you can declare them with interactive, it's better.



>> But since you want to force the arguments when it's called interactively
>> without an active region, you will have to duplicate some code.
>
> This I don't understand.  (Though I /do/ have some duplication, see
> below.)

In this case, the duplication is minime, since it's only the
(setf start (point-min) end (point-max)) in the code, and hidden in the
lambda list.


>> Separating the function and command is probablyh preferable in your
>> situation.
>>
>>     (require 'cl)
>>     (defun* my-command (&optional (start (point-min)) (end (point-max)))
>>        (interactive "r")
>>        (when (and (called-interactively-p)
>>                   (not (use-region-p)))
>>           (setf start (point-min)
>>                 end   (point-max)))
>>        …)
>
> No offence, but this seems plain ugly for me, especially the setf part.

Absolutely.  This works better when you don't need this
called-interactive-p case.


> IMHO, using the (interactive) form to define default arguments is more
> elegant, though of course I also have some duplicate code (point-min and
> point-max appear twice - though for different reasons, so to speak -
> which I don't like).  I can't see why your proposal is better - I would
> prefer to use defun and not defun*, and the Emacs manual says it's
> better to use the interactive form and not called-interactively-p (and
> I can see the reason).

I hope we all do.

-- 
__Pascal Bourguignon__                 http://www.informatimago.com/
“The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.” -- Carl Bass CEO Autodesk


  parent reply	other threads:[~2015-01-14  2:04 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <mailman.17860.1421186716.1147.help-gnu-emacs@gnu.org>
2015-01-13 22:38 ` How to write the "interactive" form for a command acting on a region Pascal J. Bourguignon
2015-01-13 23:06   ` Marcin Borkowski
     [not found]   ` <mailman.17861.1421190402.1147.help-gnu-emacs@gnu.org>
2015-01-14  2:04     ` Pascal J. Bourguignon [this message]
2015-01-14  3:49       ` Drew Adams
2015-01-13 22:05 Marcin Borkowski
2015-01-13 23:24 ` Nicolas Richard
2015-01-13 23:35   ` Marcin Borkowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87lhl6w212.fsf@kuiper.lan.informatimago.com \
    --to=pjb@informatimago.com \
    --cc=help-gnu-emacs@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).