unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Drew Adams <drew.adams@oracle.com>
To: Kaushal Modi <kaushal.modi@gmail.com>,
	Stefan Monnier <monnier@iro.umontreal.ca>,
	emacs-devel@gnu.org
Subject: Default value of variables named `*-function'    [was: Change in files.el]
Date: Tue, 31 Jan 2017 10:40:10 -0800 (PST)	[thread overview]
Message-ID: <82b4b3b7-ff61-42a7-b9fb-df3cc26a3f30@default> (raw)
In-Reply-To: <CAFyQvY1gvm_7NbdCUkqYihDZSxiB0HQL+2V5erUVsgosT1yijg@mail.gmail.com>

> I would appreciate the consistency of variables named
> with -function/-predicate suffixes to have a function
> has a default value. Being able to use add-function on
> just variables is very convenient (I use it to tweak
> region-extract-function). The default values also serve
> as good examples.

My comment here is not about providing an implicit/automatic
default value.  (I doubt that that is needed or a good idea,
but I could be wrong.)

My comment is that the default value for a given variable
whose value is intended to be a (single) function _should
usually_ be function `ignore'.  Not implicitly (see previous
paragraph) but explicitly: (defvar foo-function 'ignore).

TL;DR:
Start `-function' vars out with value `ignore', possibly
advised, so `remove-function' can get you back to a no-op.

But please read on...

If the default value needs to be a function that does
something (which is common), then instead of using, as
the default value, the function that provides the default
behavior, use `ignore' as the default value AND advise it
immediately with the function that provides the default
behavior.

Why?  Because then you can use `remove-function' to get
a no-op.  You need not set or bind the variable to
'ignore' to do that; just repeated `remove-function'
will get you to the no-op `ignore'.

`remove-function' is analogous to setting a cons-valued 
variable to its cdr.  If `(cdr (last xs))' is not nil
then xs is not a true list (it is dotted).  Using
`ignore' as the starting point for function advice is like 
using () as the starting point (the last cdr) of a list.
Function  `ignore' is more or less to advice what () is to
a list of functions - an identity element.

Advising the function that is the value of a `*-function'
variable gives you the possibility of, in effect,
applying any number of functions.  Using `:after-while'
or `:before-until', for example, gives you much the same
effect as you get with a normal or abnormal hook (as
opposed to a `*-function' hook).

But there is this big difference between a hook whose
value is a list of functions (a la `add-hook') and a
`*-function' variable whose value is a function that is
advised with, say, `:after-while':

With a hook that is a list of functions, you can remove
them all using `remove-hook'.  With an advised function,
the value is always a function.  You can of course
change the value to `ignore', but you cannot, just using
`remove-function', get to a no-op function.

Unless, that is, the starting point (the default value)
is `ignore'.  Hence my suggestion: Start with `ignore',
advising it immediately if some default behavior is
called for.

That lets users and code use just `add-function' and
`remove-function' to get the effect they have with
`add-hook' and `remove-hook': a (possibly empty)
sequence of functions applied in order.

I pointed this out in a parenthical remark in this post:
http://lists.gnu.org/archive/html/emacs-devel/2016-10/msg00415.html

    (Note that one difference from a hook is that a hook
    does not privilege the first hook function in any way
    (or the last, depending on how you look at it).
    Removing advice is not equivalent to `remove-hook'.
    It never "empties the hook" completely - the function
    that was advised is still there after removal of all
    advice.)

Here's a case in point:

Variable `isearch-filter-predicate' has a function value
or nil.  This is legacy.  Users and code can still bind
it or set it to nil.

But they cannot set it to a list of functions.  IOW, it
is basically a single-function variable (which would
normally be named `*-function') - except for the ability
to use nil instead.

I have code that lets you (interactively) add to, remove
from, etc. the behavior of `isearch-filter-predicate' on
the fly.  For more info, see
https://www.emacswiki.org/emacs/DynamicIsearchFiltering.

You can hit a key to change the current filter predicate
in ways like these:

C-z -   Remove a predicate that you specify (default:
        last-added predicate).
C-z &   Add a predicate, AND-ing it as an `:after-while'
        filter.
C-z %   Add a predicate that requires search hits to
        match a regexp you provide.
C-z ||  Add a predicate, OR-ing it as a `:before-until'
        filter.
C-z |1  Replace the last-added filter by OR-ing it with
        another.
C-z ~~  Complement the overall filter predicate.
C-z ~1  Replace the last-added filter by its complement.
C-z !   Set the overall filter predicate.
C-z 0   Reset the overall filter predicate to its default
        value (`isearch-filter-visible').
C-z c   Add a predicate that limits search between two
        columns that you specify.
C-z @   Add a predicate that constrains searching within a 
        given distance of (near) another search pattern
        (you specify the distance and pattern).

Since the default value of `isearch-filter-predicate' is
not `ignore' but `isearch-filter-visible', you cannot just
add and remove advice to get a no-op behavior or a behavior
that is not a modification of `isearch-filter-visible'.
Instead, you must set (or bind) `isearch-filter-predicate'.

This is a defect, resulting from legacy: (1) using nil
instead of `ignore' as the no-op filter and (2) using
`isearch-filter-visible' instead of `ignore' as the
default filter.

It would be nice to be able to just add & remove advice,
to control the behavior.  But that always acts on
function `isearch-filter-visible', not on `ignore'.

Code should of course be _able_ to set or bind the
variable value - no question about that.  But things
are more flexible if you can _also_ do pretty much
anything to modify the behavior using just advice.

If my suggestion were applied to the case of
`isearch-filter-predicate' then (1) it would be renamed
(e.g., to `isearch-filter-function'), and (2) its
default value would be `ignore'.

Now, because `isearch-filter-predicate' is legacy,
(1) the variable renaming would need to deprecate (but
continue to support) the old name, and (2) the variable
value nil would need to continue to be supported.

This is just an example, to try to get across the
suggestion that (1) we use `ignore' as the (explicit,
not automatic/implied) default value of variables named
`*-function', and (2) for any such variable whose
default behavior needs to be a no-op, we immediately
(i.e., by default) advise it to provide that default
behavior.

That gives users & code two ways to change it to a no-op:
(a) set the variable value to `ignore' or (b) remove all
advice from the default function (`ignore').

In a way, this amounts to saying that what is said
(correctly) in (elisp) `Advice combinators' about
various kinds of advice being "comparable for
single-function hooks to (add-hook..." is not also true
for `remove-function' - unless the starting point is
function `ignore'.  Repeatedly applying `remove-hook'
will eventually get you to (), but repeatedly applying
`remove-function' will not get you to `ignore', unless...

You might point out that if a user or code _does_ just
set the value of such a variable to some other function
than `ignore' then `remove-function' will _not_ get back
to `ignore'.  Clearly.  The effect of setting or (with a
little more work) binding can be had using just advice,
but nothing prevents someone from not using advice to
change the value.

Dunno whether I was clear or I am missing something
important.  But WDOT?



  reply	other threads:[~2017-01-31 18:40 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-01-28  2:16 Change in files.el Richard Stallman
2017-01-28  2:46 ` Stefan Monnier
2017-01-28  9:10   ` Eli Zaretskii
2017-01-28 14:40     ` Stefan Monnier
2017-01-28 14:57       ` Eli Zaretskii
2017-01-28 15:31         ` Dmitry Gutov
2017-01-28 16:12           ` Eli Zaretskii
2017-01-28 15:40         ` Stefan Monnier
2017-01-28 16:08           ` Eli Zaretskii
2017-01-28 16:51             ` Stefan Monnier
2017-01-28 17:11               ` Eli Zaretskii
2017-01-28 17:22                 ` Stefan Monnier
2017-01-28 17:30                   ` Eli Zaretskii
2017-01-28 17:42                     ` Stefan Monnier
2017-01-28 17:53                       ` Eli Zaretskii
2017-01-29 18:59                         ` John Wiegley
2017-01-30  3:57                           ` Leo Liu
2017-01-30 15:58                             ` John Wiegley
2017-01-31  4:19                               ` Leo Liu
2017-01-31 14:01                                 ` John Wiegley
2017-01-31 14:46                               ` Stefan Monnier
2017-01-31 16:21                                 ` Kaushal Modi
2017-01-31 18:40                                   ` Drew Adams [this message]
2017-02-01  8:35                                     ` Default value of variables named `*-function' [was: Change in files.el] Andreas Röhler
2017-01-28 18:41                       ` Change in files.el Mark Oteiza
2017-01-28 19:37                         ` Eli Zaretskii
2017-02-01  3:49                           ` Mark Oteiza
2017-02-01  7:33                             ` Clément Pit-Claudel
2017-02-01 12:56                               ` Eli Zaretskii
2017-02-01 14:12                                 ` Kaushal Modi
2017-01-29  0:21       ` Richard Stallman
2017-02-04  9:18 ` Eli Zaretskii
2017-02-04 23:52   ` Richard Stallman

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=82b4b3b7-ff61-42a7-b9fb-df3cc26a3f30@default \
    --to=drew.adams@oracle.com \
    --cc=emacs-devel@gnu.org \
    --cc=kaushal.modi@gmail.com \
    --cc=monnier@iro.umontreal.ca \
    /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.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

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