unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* 7 logical-xor implementations in source tree
@ 2019-07-22 18:48 Oleh Krehel
  2019-07-22 21:47 ` Basil L. Contovounesios
  2019-07-23  8:39 ` Andy Moreton
  0 siblings, 2 replies; 51+ messages in thread
From: Oleh Krehel @ 2019-07-22 18:48 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 457 bytes --]

Hi all,

I found myself in need of a logical-xor function. There's `xor' 
defined in
array.el. But I don't want my package to depend on array.el. So I 
grepped the
git source of some other definition of `xor'. And I found 7 
results: 7 functions
doing the same thing, none of them in a satisfactory place.

Should we figure out where to put the "main" `xor' and declare the 
rest as
obsolete aliases?

I attach the occurrences that I found.

regards,
Oleh


[-- Attachment #2: emacs-logical-xor-functions.txt --]
[-- Type: text/plain, Size: 404 bytes --]

-*- mode:grep; -*-


7 candidates:
./lisp/array.el:743:(defun xor (pred1 pred2)
./lisp/gnus/spam.el:711:(defun spam-xor (a b)
./lisp/org/org.el:10071:(defun org-xor (a b)
./lisp/play/5x5.el:934:(defun 5x5-xor (x y)
./lisp/proced.el:1197:(defsubst proced-xor (b1 b2)
./lisp/progmodes/idlwave.el:8816:(defmacro idlwave-xor (a b)
./lisp/vc/diff-mode.el:1773:(defsubst diff-xor (a b) (if a (if (not b) a) b))

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-22 18:48 7 logical-xor implementations in source tree Oleh Krehel
@ 2019-07-22 21:47 ` Basil L. Contovounesios
  2019-07-24 22:17   ` Basil L. Contovounesios
  2019-07-23  8:39 ` Andy Moreton
  1 sibling, 1 reply; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-22 21:47 UTC (permalink / raw)
  To: Oleh Krehel; +Cc: emacs-devel

Oleh Krehel <oleh@oremacs.com> writes:

> I found myself in need of a logical-xor function. There's `xor' defined in
> array.el. But I don't want my package to depend on array.el. So I grepped the
> git source of some other definition of `xor'. And I found 7 results: 7 functions
> doing the same thing, none of them in a satisfactory place.
>
> Should we figure out where to put the "main" `xor' and declare the rest as
> obsolete aliases?

Yes please!  I've had a patch to this effect sitting on a local branch
for a while now.  Will touch it up and send soon.

Thanks,

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-22 18:48 7 logical-xor implementations in source tree Oleh Krehel
  2019-07-22 21:47 ` Basil L. Contovounesios
@ 2019-07-23  8:39 ` Andy Moreton
  2019-07-23  9:08   ` Basil L. Contovounesios
  2019-07-23  9:14   ` Mattias Engdegård
  1 sibling, 2 replies; 51+ messages in thread
From: Andy Moreton @ 2019-07-23  8:39 UTC (permalink / raw)
  To: emacs-devel

On Mon 22 Jul 2019, Oleh Krehel wrote:

> Hi all,
>
> I found myself in need of a logical-xor function. There's `xor' defined in
> array.el. But I don't want my package to depend on array.el. So I grepped the
> git source of some other definition of `xor'. And I found 7 results: 7
> functions
> doing the same thing, none of them in a satisfactory place.

Is `logxor' what you are looking for ? Removing duplication would be
useful.

    AndyM




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23  8:39 ` Andy Moreton
@ 2019-07-23  9:08   ` Basil L. Contovounesios
  2019-07-23  9:14   ` Mattias Engdegård
  1 sibling, 0 replies; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-23  9:08 UTC (permalink / raw)
  To: Andy Moreton; +Cc: emacs-devel

Andy Moreton <andrewjmoreton@gmail.com> writes:

> On Mon 22 Jul 2019, Oleh Krehel wrote:
>
>> I found myself in need of a logical-xor function. There's `xor' defined in
>> array.el. But I don't want my package to depend on array.el. So I grepped the
>> git source of some other definition of `xor'. And I found 7 results: 7
>> functions
>> doing the same thing, none of them in a satisfactory place.
>
> Is `logxor' what you are looking for ?

No, logxor is a bitwise operation on integers, whereas the proposed xor
operates on booleans, returning non-nil only if one of its arguments is
nil and the other is non-nil.

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23  8:39 ` Andy Moreton
  2019-07-23  9:08   ` Basil L. Contovounesios
@ 2019-07-23  9:14   ` Mattias Engdegård
  2019-07-23 10:26     ` Andy Moreton
                       ` (3 more replies)
  1 sibling, 4 replies; 51+ messages in thread
From: Mattias Engdegård @ 2019-07-23  9:14 UTC (permalink / raw)
  To: Andy Moreton; +Cc: emacs-devel

23 juli 2019 kl. 10.39 skrev Andy Moreton <andrewjmoreton@gmail.com>:
> 
> Is `logxor' what you are looking for ? Removing duplication would be
> useful.

Pretty sure he meant boolean (nil/non-nil) xor, not bitwise xor.

Some naïve suggestions:

* Call it `xor', to go with `and' and `or'
* Make it n-adic, taking at least 1 argument (since the operation has no identity)
* Make it a function, so that it can be used with `apply' etc.
* Give it a compiler macro, for efficient partial application

There is probably already something like this in CL, like everything else.




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23  9:14   ` Mattias Engdegård
@ 2019-07-23 10:26     ` Andy Moreton
  2019-07-23 10:40       ` Andreas Schwab
  2019-07-23 10:44     ` Basil L. Contovounesios
                       ` (2 subsequent siblings)
  3 siblings, 1 reply; 51+ messages in thread
From: Andy Moreton @ 2019-07-23 10:26 UTC (permalink / raw)
  To: emacs-devel

On Tue 23 Jul 2019, Mattias Engdegård wrote:

> 23 juli 2019 kl. 10.39 skrev Andy Moreton <andrewjmoreton@gmail.com>:
>> 
>> Is `logxor' what you are looking for ? Removing duplication would be
>> useful.
>
> Pretty sure he meant boolean (nil/non-nil) xor, not bitwise xor.

Yes. The naming of the bitwise operations `logand', `logior', `logxor'
and `lognot' is unfortunate.

> Some naïve suggestions:
>
> * Call it `xor', to go with `and' and `or'
> * Make it n-adic, taking at least 1 argument (since the operation has no identity)
> * Make it a function, so that it can be used with `apply' etc.
> * Give it a compiler macro, for efficient partial application

Perhaps an `xor' special form in eval.c next to `and' and `or' ?

    AndyM




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 10:26     ` Andy Moreton
@ 2019-07-23 10:40       ` Andreas Schwab
  2019-07-23 12:11         ` Andy Moreton
  0 siblings, 1 reply; 51+ messages in thread
From: Andreas Schwab @ 2019-07-23 10:40 UTC (permalink / raw)
  To: Andy Moreton; +Cc: emacs-devel

On Jul 23 2019, Andy Moreton <andrewjmoreton@gmail.com> wrote:

> Perhaps an `xor' special form in eval.c next to `and' and `or' ?

There is no need for a special form since xor needs to evaluate all its
arguments anyway.

Andreas.

-- 
Andreas Schwab, SUSE Labs, schwab@suse.de
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE  1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23  9:14   ` Mattias Engdegård
  2019-07-23 10:26     ` Andy Moreton
@ 2019-07-23 10:44     ` Basil L. Contovounesios
  2019-07-23 11:01       ` Yuri Khan
  2019-07-25 21:46       ` Juri Linkov
  2019-07-23 11:24     ` Noam Postavsky
  2019-07-23 12:38     ` Stefan Monnier
  3 siblings, 2 replies; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-23 10:44 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: Andy Moreton, emacs-devel

Mattias Engdegård <mattiase@acm.org> writes:

> Some naïve suggestions:
>
> * Call it `xor', to go with `and' and `or'

+1

> * Make it n-adic, taking at least 1 argument (since the operation has no identity)

That depends on the preferred semantics, right?  What semantics are you
suggesting?  Judging from online discussions I've come across, there's
more than one way to skin this cat.

I'm not convinced an n-ary xor function would be that useful in Emacs,
but I'm not opposed to it at all.  I'm interested to see arguments for
and examples of it.

Most people seem to define n-ary xor as a parity check (i.e. its result
is non-nil if the number of non-nil arguments is odd).

How often does this specific need crop up in Emacs that can't already
be handled more generally by bool-vectors or custom code like
(cl-oddp (length (delq nil list)))?

OTOH, a definition like the following:

  (defsubst xor (cond1 cond2)
    (cond ((not cond1) cond2)
          ((not cond2) cond1)))

not only performs simple logical xor more efficiently, but also returns
whichever of the two arguments is non-nil (though I can't imagine when
this last detail would ever be useful).

> * Make it a function, so that it can be used with `apply' etc.

Definitely, using either defsubst or define-inline.

> * Give it a compiler macro, for efficient partial application

That's already done by define-inline, right?  Since I'm not familiar
with compiler macros, can you please explain how they afford efficient
partial application?

> There is probably already something like this in CL, like everything else.

I'm not a CL expert, but some quick online searching did not reveal
much.  I had previously taken inspiration from Racket's boolean
operators, though: https://docs.racket-lang.org/reference/booleans.html

Thanks,

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 10:44     ` Basil L. Contovounesios
@ 2019-07-23 11:01       ` Yuri Khan
  2019-07-25 21:46       ` Juri Linkov
  1 sibling, 0 replies; 51+ messages in thread
From: Yuri Khan @ 2019-07-23 11:01 UTC (permalink / raw)
  To: Basil L. Contovounesios
  Cc: Mattias Engdegård, Andy Moreton, Emacs developers

On Tue, Jul 23, 2019 at 5:44 PM Basil L. Contovounesios <contovob@tcd.ie> wrote:

> Most people seem to define n-ary xor as a parity check (i.e. its result
> is non-nil if the number of non-nil arguments is odd).

The other interpretation, based on the literal meaning of “exclusive
or”, is that the result is non-nil if exactly one argument is non-nil.
(Could actually return that one argument as the result.)

With both interpretations, a vacuous xor is nil.



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23  9:14   ` Mattias Engdegård
  2019-07-23 10:26     ` Andy Moreton
  2019-07-23 10:44     ` Basil L. Contovounesios
@ 2019-07-23 11:24     ` Noam Postavsky
  2019-07-23 12:38     ` Stefan Monnier
  3 siblings, 0 replies; 51+ messages in thread
From: Noam Postavsky @ 2019-07-23 11:24 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: Andy Moreton, Emacs developers

> * Make it n-adic, taking at least 1 argument (since the operation has no identity)

We have 7 implementations and all of them take exactly 2 arguments; so
I think this generality is not needed.



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 10:40       ` Andreas Schwab
@ 2019-07-23 12:11         ` Andy Moreton
  2019-07-23 13:20           ` Basil L. Contovounesios
  0 siblings, 1 reply; 51+ messages in thread
From: Andy Moreton @ 2019-07-23 12:11 UTC (permalink / raw)
  To: emacs-devel

On Tue 23 Jul 2019, Andreas Schwab wrote:

> On Jul 23 2019, Andy Moreton <andrewjmoreton@gmail.com> wrote:
>
>> Perhaps an `xor' special form in eval.c next to `and' and `or' ?
>
> There is no need for a special form since xor needs to evaluate all its
> arguments anyway.

You are right for a two argument xor. However for a multi-argument xor,
the remaining arguments can be ignored after a second non-nil argument
is evaluated.

    AndyM





^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23  9:14   ` Mattias Engdegård
                       ` (2 preceding siblings ...)
  2019-07-23 11:24     ` Noam Postavsky
@ 2019-07-23 12:38     ` Stefan Monnier
  2019-07-23 16:41       ` Mattias Engdegård
  3 siblings, 1 reply; 51+ messages in thread
From: Stefan Monnier @ 2019-07-23 12:38 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: Andy Moreton, emacs-devel

> * Call it `xor', to go with `and' and `or'

Fine.

> * Make it n-adic, taking at least 1 argument (since the operation has no identity)

I'm not convinced it's worth the trouble (we only have 7 uses so far
and they all seem happy with a 2-arg xor).
Especially since there are 2 different reasonable semantics.
It can always be extended later if needed.

> * Make it a function, so that it can be used with `apply' etc.

It's naturally a function, indeed.

> * Give it a compiler macro, for efficient partial application

Given how rarely it's used, I'm not sure it's worth the trouble.
Luckily if we only accept the 2-args case this can be done very cheaply
with defsubst or define-inline.


        Stefan




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 12:11         ` Andy Moreton
@ 2019-07-23 13:20           ` Basil L. Contovounesios
  2019-07-23 13:54             ` Andy Moreton
  0 siblings, 1 reply; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-23 13:20 UTC (permalink / raw)
  To: Andy Moreton; +Cc: emacs-devel

Andy Moreton <andrewjmoreton@gmail.com> writes:

> On Tue 23 Jul 2019, Andreas Schwab wrote:
>
>> On Jul 23 2019, Andy Moreton <andrewjmoreton@gmail.com> wrote:
>>
>>> Perhaps an `xor' special form in eval.c next to `and' and `or' ?
>>
>> There is no need for a special form since xor needs to evaluate all its
>> arguments anyway.
>
> You are right for a two argument xor. However for a multi-argument xor,
> the remaining arguments can be ignored after a second non-nil argument
> is evaluated.

No, xor must evaluate all of its arguments in all n-ary interpretations
suggested so far:

- Odd number of non-nil arguments
- Exactly one non-nil argument

Neither of these conditions can be known without examining the entire
arglist.

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 13:20           ` Basil L. Contovounesios
@ 2019-07-23 13:54             ` Andy Moreton
  2019-07-24 22:21               ` Basil L. Contovounesios
  0 siblings, 1 reply; 51+ messages in thread
From: Andy Moreton @ 2019-07-23 13:54 UTC (permalink / raw)
  To: emacs-devel

On Tue 23 Jul 2019, Basil L. Contovounesios wrote:

> Andy Moreton <andrewjmoreton@gmail.com> writes:
>
>> On Tue 23 Jul 2019, Andreas Schwab wrote:
>>
>>> On Jul 23 2019, Andy Moreton <andrewjmoreton@gmail.com> wrote:
>>>
>>>> Perhaps an `xor' special form in eval.c next to `and' and `or' ?
>>>
>>> There is no need for a special form since xor needs to evaluate all its
>>> arguments anyway.
>>
>> You are right for a two argument xor. However for a multi-argument xor,
>> the remaining arguments can be ignored after a second non-nil argument
>> is evaluated.
>
> No, xor must evaluate all of its arguments in all n-ary interpretations
> suggested so far:
>
> - Odd number of non-nil arguments
Agreed.

> - Exactly one non-nil argument
After two non-nil arguments have been evaluated, there is no need to
evaluate the remaining arguments.

    AndyM




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 12:38     ` Stefan Monnier
@ 2019-07-23 16:41       ` Mattias Engdegård
  2019-07-23 17:12         ` Eli Zaretskii
  2019-07-23 17:48         ` Paul Eggert
  0 siblings, 2 replies; 51+ messages in thread
From: Mattias Engdegård @ 2019-07-23 16:41 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Andy Moreton, emacs-devel

23 juli 2019 kl. 14.38 skrev Stefan Monnier <monnier@iro.umontreal.ca>:
> 
> I'm not convinced it's worth the trouble (we only have 7 uses so far
> and they all seem happy with a 2-arg xor).
> Especially since there are 2 different reasonable semantics.
> It can always be extended later if needed.

That's reasonable. Racket made it 2-arg (thanks for the reference, Basil); looks like they couldn't make up their minds either.

>> * Give it a compiler macro, for efficient partial application
> 
> Given how rarely it's used, I'm not sure it's worth the trouble.
> Luckily if we only accept the 2-args case this can be done very cheaply
> with defsubst or define-inline.

Yes, define-inline in particular will produce decent code.

I would probably use the negation, boolean equivalence, more often than xor myself.
Since `equiv' (placeholder name) is more readily thought of as an equivalence predicate, its n-ary semantics is subject to less debate.

Example implementations:

+(define-inline xor (arg1 arg2)
+  "Boolean exclusive-or: the non-nil argument if the other is nil, else nil."
+  (inline-letevals (arg1 arg2)
+    (inline-quote
+     (if ,arg1
+         (if ,arg2
+             nil
+           ,arg1)
+       ,arg2))))
+
+(defmacro equiv (&rest args)
+  "Boolean equivalence: t if arguments are all non-nil or all nil."
+  (cond ((null args) t)
+        ((null (cdr args)) `(progn ,(car args) t))
+        (t `(if ,(car args)
+                (and ,@(append (cdr args) '(t)))
+              (not (or ,@(cdr args)))))))




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 16:41       ` Mattias Engdegård
@ 2019-07-23 17:12         ` Eli Zaretskii
  2019-07-23 17:48         ` Paul Eggert
  1 sibling, 0 replies; 51+ messages in thread
From: Eli Zaretskii @ 2019-07-23 17:12 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: andrewjmoreton, monnier, emacs-devel

> From: Mattias Engdegård <mattiase@acm.org>
> Date: Tue, 23 Jul 2019 18:41:09 +0200
> Cc: Andy Moreton <andrewjmoreton@gmail.com>, emacs-devel@gnu.org
> 
> +(define-inline xor (arg1 arg2)
> +  "Boolean exclusive-or: the non-nil argument if the other is nil, else nil."

I think this doc string tries to pack too much into a single short
sentence.  How about this instead:

    "Return the boolean exclusive-or of ARG1 and ARG2.
  If only one of the two arguments is non-nil, the function returns
  that argument; otherwise it returns nil."

Thanks.



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 16:41       ` Mattias Engdegård
  2019-07-23 17:12         ` Eli Zaretskii
@ 2019-07-23 17:48         ` Paul Eggert
  2019-07-23 19:45           ` Stefan Monnier
  1 sibling, 1 reply; 51+ messages in thread
From: Paul Eggert @ 2019-07-23 17:48 UTC (permalink / raw)
  To: Mattias Engdegård, Stefan Monnier; +Cc: Andy Moreton, Emacs Development

Mattias Engdegård wrote:

> I would probably use the negation, boolean equivalence, more often than xor myself.

Me too. This was also Dijkstra's opinion (I attended an infamous lecture by him 
on the subject).

> define-inline in particular will produce decent code.

I don't think performance is much of a concern here, and would favor defining 
them as functions instead; that's simpler and allows them to be used in more 
contexts when code is written using a functional style.

> +(defmacro equiv (&rest args)
> +  "Boolean equivalence: t if arguments are all non-nil or all nil."

When equiv has one or more arguments and they are all non-nil, it might be more 
useful for equiv to return its last argument instead of returning t. Of course 
I'm bike-shedding here....



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 17:48         ` Paul Eggert
@ 2019-07-23 19:45           ` Stefan Monnier
  0 siblings, 0 replies; 51+ messages in thread
From: Stefan Monnier @ 2019-07-23 19:45 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Mattias Engdegård, Andy Moreton, Emacs Development

>> define-inline in particular will produce decent code.
>
> I don't think performance is much of a concern here, and would favor
> defining them as functions instead; that's simpler and allows them to be
> used in more contexts when code is written using a functional style.

I'm not advocating the use of `define-inline` for this case, but just
want to clarify that `define-inline` does define a *function*, just like
`defsubst` does.


        Stefan




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-22 21:47 ` Basil L. Contovounesios
@ 2019-07-24 22:17   ` Basil L. Contovounesios
  2019-07-24 23:15     ` Stefan Monnier
  0 siblings, 1 reply; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-24 22:17 UTC (permalink / raw)
  To: Oleh Krehel; +Cc: emacs-devel

[-- Attachment #1: 0001-Add-conditional-operators-xor-and-equiv-to-subr.el.patch --]
[-- Type: text/x-diff, Size: 21713 bytes --]

From 78a4b6153315b6c83af5d802a71eb15734c0a07f Mon Sep 17 00:00:00 2001
From: "Basil L. Contovounesios" <contovob@tcd.ie>
Date: Tue, 9 Apr 2019 02:23:41 +0100
Subject: [PATCH] Add conditional operators xor and equiv to subr.el
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Suggested by Oleh Krehel <oleh@oremacs.com> and provided by Mattias
Engdegård <mattiase@acm.org> in the following thread:
https://lists.gnu.org/archive/html/emacs-devel/2019-07/msg00547.html

* lisp/array.el (xor): Move unused function from here...
* lisp/subr.el: ...to here.
(equiv): New macro.
* lisp/gnus/spam.el (spam-xor):
* lisp/play/5x5.el (5x5-xor):
* lisp/proced.el (proced-xor):
* lisp/progmodes/idlwave.el (idlwave-xor):
* lisp/vc/diff-mode.el (diff-xor): Define as obsolete aliases of,
and replace all uses with, xor.
* lisp/jsonrpc.el: Remove unused dependency on array.el.
* lisp/org/org.el (org-xor): Move from here...
* lisp/org/org-compat.el (org-xor): ...to here, as a compatibility
shim for xor.
* lisp/progmodes/idlw-shell.el (idlwave-shell-enable-all-bp):
* lisp/simple.el (exchange-point-and-mark): Use equiv.
* lisp/strokes.el (strokes-xor): Remove commented-out xor
implementation.
* lisp/windmove.el (windmove-display-in-direction): Use xor.

* doc/lispref/control.texi (Control Structures): Extend menu entry
for new combining conditions.
(Combining Conditions):
* etc/NEWS (Lisp Changes): Document xor and equiv.

* test/lisp/subr-tests.el (subr-test-xor, subr-test-equiv): New
tests.
---
 doc/lispref/control.texi     | 64 ++++++++++++++++++++++++++++++++++--
 etc/NEWS                     | 12 +++++++
 lisp/array.el                |  5 ---
 lisp/gnus/spam.el            |  6 ++--
 lisp/jsonrpc.el              |  1 -
 lisp/org/org-compat.el       |  8 +++++
 lisp/org/org.el              |  4 ---
 lisp/play/5x5.el             |  8 ++---
 lisp/proced.el               | 11 +++----
 lisp/progmodes/idlw-shell.el |  2 +-
 lisp/progmodes/idlwave.el    | 25 +++++++-------
 lisp/simple.el               |  3 +-
 lisp/strokes.el              |  6 ----
 lisp/subr.el                 | 20 +++++++++++
 lisp/vc/diff-mode.el         | 12 +++----
 lisp/windmove.el             |  2 +-
 test/lisp/subr-tests.el      | 22 +++++++++++++
 17 files changed, 153 insertions(+), 58 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index de6cd9301f..49ad44932b 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -38,7 +38,7 @@ Control Structures
 @menu
 * Sequencing::             Evaluation in textual order.
 * Conditionals::           @code{if}, @code{cond}, @code{when}, @code{unless}.
-* Combining Conditions::   @code{and}, @code{or}, @code{not}.
+* Combining Conditions::   @code{and}, @code{or}, @code{not}, and friends.
 * Pattern-Matching Conditional::  How to use @code{pcase} and friends.
 * Iteration::              @code{while} loops.
 * Generators::             Generic sequences and coroutines.
@@ -298,8 +298,8 @@ Combining Conditions
 @section Constructs for Combining Conditions
 @cindex combining conditions
 
-  This section describes three constructs that are often used together
-with @code{if} and @code{cond} to express complicated conditions.  The
+  This section describes constructs that are often used together with
+@code{if} and @code{cond} to express complicated conditions.  The
 constructs @code{and} and @code{or} can also be used individually as
 kinds of multiple conditional constructs.
 
@@ -419,6 +419,64 @@ Combining Conditions
 @var{arg3})} never evaluates any argument more than once.
 @end defspec
 
+@defmac equiv &rest conditions
+The @code{equiv} macro tests whether all the @var{conditions} are
+logically equivalent, i.e., either all @code{nil} or all
+non-@code{nil}.  It works by evaluating the @var{conditions} one by
+one in the order written.
+
+If any of the @var{conditions} evaluates to a value logically
+different from its preceding @var{conditions}, then the result of the
+@code{equiv} must be @code{nil} regardless of the remaining
+@var{conditions}; so @code{equiv} returns @code{nil} right away,
+ignoring the remaining @var{conditions}.
+
+If all the @var{conditions} turn out non-@code{nil}, then the
+@code{equiv} expression returns the value of the last one.  Otherwise,
+if all the @var{conditions} turn out @code{nil}, @code{equiv} returns
+@code{t}.  Just @code{(equiv)}, with no @var{conditions}, also returns
+@code{t}, appropriate because all the @var{conditions} turned out
+logically equivalent.  (Think about it; which one did not?)
+
+For example, the following expression tests whether either some state
+is enabled (@var{enabled} is non-@code{nil}) and should be disabled
+(@var{disable} is also non-@code{nil}), or the state is disabled
+(@var{enabled} is @code{nil}) and should be enabled (@var{disable} is
+also @code{nil}); if either of these conditions holds, the state
+should subsequently be toggled:
+
+@example
+(when (equiv enabled disable)
+  ;; Toggle state
+  @dots{})
+@end example
+
+Like the @code{and} construct, @code{equiv} can be written in terms of
+@code{if} or @code{cond}, though not quite as naturally.  Here's how:
+
+@example
+@group
+(equiv @var{arg1} @var{arg2} @var{arg3})
+@equiv{}
+(if @var{arg1} (if @var{arg2} @var{arg3})
+  (if @var{arg2} nil
+    (if @var{arg3} nil t)))
+@equiv{}
+(cond (@var{arg1} (cond (@var{arg2} @var{arg3})))
+      (@var{arg2} nil)
+      (@var{arg3} nil)
+      (t))
+@end group
+@end example
+@end defmac
+
+@defun xor condition1 condition2
+This function returns the boolean exclusive-or of @var{condition1} and
+@var{condition2}.  That is, @code{xor} returns @code{nil} if either
+both arguments are @code{nil}, or both are non-@code{nil}.  Otherwise,
+it returns the value of that argument which is non-@code{nil}.
+@end defun
+
 @node Pattern-Matching Conditional
 @section Pattern-Matching Conditional
 @cindex pcase
diff --git a/etc/NEWS b/etc/NEWS
index 5313270411..25e5fe9574 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2390,6 +2390,18 @@ the Emacs Lisp manual.
 ** `directory-files-recursively' can now take an optional PREDICATE
 parameter to control descending into subdirectories.
 
++++
+** New function 'xor' returns the boolean exclusive-or if its args.
+The function was previously defined in array.el, but has been moved to
+subr.el so that it is available by default.  Several duplicates of
+'xor' in other packages are now obsolete aliases of 'xor'.
+
++++
+** New macro 'equiv' tests whether its args are logically equivalent.
+If its arguments are all non-nil, 'equiv' returns the value of the
+last one; if they are all nil, it returns t; otherwise, its evaluation
+short-circuits and returns nil.
+
 \f
 * Changes in Emacs 27.1 on Non-Free Operating Systems
 
diff --git a/lisp/array.el b/lisp/array.el
index 2fffe0197e..965e97ff55 100644
--- a/lisp/array.el
+++ b/lisp/array.el
@@ -740,11 +740,6 @@ limit-index
 	((> index limit) limit)
 	(t index)))
 
-(defun xor (pred1 pred2)
-  "Return the logical exclusive or of predicates PRED1 and PRED2."
-  (and (or pred1 pred2)
-       (not (and pred1 pred2))))
-
 (defun current-line ()
   "Return the current buffer line at point.  The first line is 0."
   (count-lines (point-min) (line-beginning-position)))
diff --git a/lisp/gnus/spam.el b/lisp/gnus/spam.el
index d752bf0efe..f990e0cba1 100644
--- a/lisp/gnus/spam.el
+++ b/lisp/gnus/spam.el
@@ -708,9 +708,7 @@ spam-clear-cache
   "Clear the `spam-caches' entry for a check."
   (remhash symbol spam-caches))
 
-(defun spam-xor (a b)
-  "Logical A xor B."
-  (and (or a b) (not (and a b))))
+(define-obsolete-function-alias 'spam-xor 'xor "27.1")
 
 (defun spam-set-difference (list1 list2)
   "Return a set difference of LIST1 and LIST2.
@@ -2550,7 +2548,7 @@ spam-spamoracle-learn
         (goto-char (point-min))
         (dolist (article articles)
           (insert (spam-get-article-as-string article)))
-        (let* ((arg (if (spam-xor unregister article-is-spam-p)
+        (let* ((arg (if (xor unregister article-is-spam-p)
                         "-spam"
                       "-good"))
                (status
diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
index 0fffee6866..85fd40ecd2 100644
--- a/lisp/jsonrpc.el
+++ b/lisp/jsonrpc.el
@@ -43,7 +43,6 @@
 (require 'warnings)
 (require 'pcase)
 (require 'ert) ; to escape a `condition-case-unless-debug'
-(require 'array) ; xor
 
 \f
 ;;; Public API
diff --git a/lisp/org/org-compat.el b/lisp/org/org-compat.el
index 062bb4c5ca..bb927fedf9 100644
--- a/lisp/org/org-compat.el
+++ b/lisp/org/org-compat.el
@@ -362,6 +362,14 @@ 'org-texinfo-def-table-markup
 \f
 ;;; Miscellaneous functions
 
+;; `xor' was added in Emacs 27.1.
+(defalias 'org-xor
+  (if (fboundp 'xor)
+      #'xor
+    (lambda (a b)
+      "Exclusive or."
+      (if a (not b) b))))
+
 (defun org-version-check (version feature level)
   (let* ((v1 (mapcar 'string-to-number (split-string version "[.]")))
          (v2 (mapcar 'string-to-number (split-string emacs-version "[.]")))
diff --git a/lisp/org/org.el b/lisp/org/org.el
index 5aa49b29d6..79725ac752 100644
--- a/lisp/org/org.el
+++ b/lisp/org/org.el
@@ -10068,10 +10068,6 @@ org-link-unescape-single-byte-sequence
 	       (char-to-string (string-to-number byte 16)))
 	     (cdr (split-string hex "%")) ""))
 
-(defun org-xor (a b)
-  "Exclusive or."
-  (if a (not b) b))
-
 (defun org-fixup-message-id-for-http (s)
   "Replace special characters in a message id, so it can be used in an http query."
   (when (string-match "%" s)
diff --git a/lisp/play/5x5.el b/lisp/play/5x5.el
index 28748cc351..c5d4659123 100644
--- a/lisp/play/5x5.el
+++ b/lisp/play/5x5.el
@@ -435,8 +435,8 @@ 5x5-make-xor-with-mutation
     (dotimes (y 5x5-grid-size)
       (dotimes (x 5x5-grid-size)
         (5x5-set-cell xored y x
-                      (5x5-xor (5x5-cell current y x)
-                               (5x5-cell best    y x)))))
+                      (xor (5x5-cell current y x)
+                           (5x5-cell best    y x)))))
     (5x5-mutate-solution xored)))
 
 (defun 5x5-mutate-solution (solution)
@@ -931,9 +931,7 @@ 5x5-randomize
 
 ;; Support functions
 
-(defun 5x5-xor (x y)
-  "Boolean exclusive-or of X and Y."
-  (and (or x y) (not (and x y))))
+(define-obsolete-function-alias '5x5-xor 'xor "27.1")
 
 (defun 5x5-y-or-n-p (prompt)
   "5x5 wrapper for `y-or-n-p' which respects the `5x5-hassle-me' setting."
diff --git a/lisp/proced.el b/lisp/proced.el
index b05046bfbd..f8685d3c2f 100644
--- a/lisp/proced.el
+++ b/lisp/proced.el
@@ -1194,10 +1194,7 @@ proced-time-lessp
 
 ;;; Sorting
 
-(defsubst proced-xor (b1 b2)
-  "Return the logical exclusive or of args B1 and B2."
-  (and (or b1 b2)
-       (not (and b1 b2))))
+(define-obsolete-function-alias 'proced-xor 'xor "27.1")
 
 (defun proced-sort-p (p1 p2)
   "Predicate for sorting processes P1 and P2."
@@ -1208,8 +1205,8 @@ proced-sort-p
              (k2 (cdr (assq (car sorter) (cdr p2)))))
         ;; if the attributes are undefined, we should really abort sorting
         (if (and k1 k2)
-            (proced-xor (funcall (nth 1 sorter) k1 k2)
-                        (nth 2 sorter))))
+            (xor (funcall (nth 1 sorter) k1 k2)
+                 (nth 2 sorter))))
     (let ((sort-list proced-sort-internal) sorter predicate k1 k2)
       (catch 'done
         (while (setq sorter (pop sort-list))
@@ -1219,7 +1216,7 @@ proced-sort-p
                 (if (and k1 k2)
                     (funcall (nth 1 sorter) k1 k2)))
           (if (not (eq predicate 'equal))
-              (throw 'done (proced-xor predicate (nth 2 sorter)))))
+              (throw 'done (xor predicate (nth 2 sorter)))))
         (eq t predicate)))))
 
 (defun proced-sort (process-alist sorter descend)
diff --git a/lisp/progmodes/idlw-shell.el b/lisp/progmodes/idlw-shell.el
index 3bd99620d0..568479c822 100644
--- a/lisp/progmodes/idlw-shell.el
+++ b/lisp/progmodes/idlw-shell.el
@@ -2604,7 +2604,7 @@ idlwave-shell-enable-all-bp
   (let  ((bpl (or bpl idlwave-shell-bp-alist)) disabled modified)
     (while bpl
       (setq disabled (idlwave-shell-bp-get (car bpl) 'disabled))
-      (when (idlwave-xor (not disabled) (eq enable 'enable))
+      (when (equiv disabled (eq enable 'enable))
 	(idlwave-shell-toggle-enable-current-bp
 	 (car bpl) (if (eq enable 'enable) 'enable 'disable) no-update)
 	(push (car bpl) modified))
diff --git a/lisp/progmodes/idlwave.el b/lisp/progmodes/idlwave.el
index 614d73e23b..1b4b55c94f 100644
--- a/lisp/progmodes/idlwave.el
+++ b/lisp/progmodes/idlwave.el
@@ -8813,9 +8813,8 @@ idlwave-study-twins
 
 ;; FIXME: Dynamically scoped vars need to use the `idlwave-' prefix.
 ;; (defvar type)
-(defmacro idlwave-xor (a b)
-  `(and (or ,a ,b)
-	(not (and ,a ,b))))
+
+(define-obsolete-function-alias 'idlwave-xor 'xor "27.1")
 
 (defun idlwave-routine-entry-compare (a b)
   "Compare two routine info entries for sorting.
@@ -8919,17 +8918,17 @@ idlwave-routine-twin-compare
     ;; Now: follow JD's ideas about sorting.  Looks really simple now,
     ;; doesn't it?  The difficult stuff is hidden above...
     (cond
-     ((idlwave-xor asysp  bsysp)       asysp)	; System entries first
-     ((idlwave-xor aunresp bunresp)    bunresp) ; Unresolved last
+     ((xor asysp   bsysp)     asysp)        ; System entries first
+     ((xor aunresp bunresp)   bunresp)      ; Unresolved last
      ((and idlwave-sort-prefer-buffer-info
-	   (idlwave-xor abufp bbufp))  abufp)	; Buffers before non-buffers
-     ((idlwave-xor acompp bcompp)      acompp)	; Compiled entries
-     ((idlwave-xor apathp bpathp)      apathp)	; Library before non-library
-     ((idlwave-xor anamep bnamep)      anamep)	; Correct file names first
-     ((and idlwave-twin-class anamep bnamep     ; both file names match ->
-	   (idlwave-xor adefp bdefp))  bdefp)	; __define after __method
-     ((> anpath bnpath)                t)	; Who is first on path?
-     (t                                nil))))	; Default
+           (xor abufp bbufp)) abufp)        ; Buffers before non-buffers
+     ((xor acompp bcompp)     acompp)       ; Compiled entries
+     ((xor apathp bpathp)     apathp)       ; Library before non-library
+     ((xor anamep bnamep)     anamep)       ; Correct file names first
+     ((and idlwave-twin-class anamep bnamep ; both file names match ->
+           (xor adefp bdefp)) bdefp)        ; __define after __method
+     ((> anpath bnpath)       t)            ; Who is first on path?
+     (t                       nil))))       ; Default
 
 (defun idlwave-routine-source-file (source)
   (if (nth 2 source)
diff --git a/lisp/simple.el b/lisp/simple.el
index e33709e8ad..bc5a802930 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -5847,8 +5847,7 @@ exchange-point-and-mark
     (goto-char omark)
     (cond (temp-highlight
 	   (setq-local transient-mark-mode (cons 'only transient-mark-mode)))
-	  ((or (and arg (region-active-p)) ; (xor arg (not (region-active-p)))
-	       (not (or arg (region-active-p))))
+          ((equiv arg (region-active-p))
 	   (deactivate-mark))
 	  (t (activate-mark)))
     nil))
diff --git a/lisp/strokes.el b/lisp/strokes.el
index 0c671c43ac..6edf58c7b6 100644
--- a/lisp/strokes.el
+++ b/lisp/strokes.el
@@ -1524,12 +1524,6 @@ strokes-xpm-char-bit-p
   (or (eq char ?\s)
       (eq char ?*)))
 
-;;(defsubst strokes-xor (a b)  ### Should I make this an inline function? ###
-;;  "T if one and only one of A and B is non-nil; otherwise, returns nil.
-;;NOTE: Don't use this as a numeric xor since it treats all non-nil
-;;      values as t including `0' (zero)."
-;;  (eq (null a) (not (null b))))
-
 (defsubst strokes-xpm-encode-length-as-string (length)
   "Given some LENGTH in [0,62) do a fast lookup of its encoding."
   (aref strokes-base64-chars length))
diff --git a/lisp/subr.el b/lisp/subr.el
index f1a4e8bb29..e922fe739f 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -209,6 +209,26 @@ unless
   (declare (indent 1) (debug t))
   (cons 'if (cons cond (cons nil body))))
 
+(defsubst xor (cond1 cond2)
+  "Return the boolean exclusive-or of COND1 and COND2.
+If only one of the arguments is non-nil, return it; otherwise
+return nil."
+  (declare (pure t) (side-effect-free error-free))
+  (cond ((not cond1) cond2)
+        ((not cond2) cond1)))
+
+(defmacro equiv (&rest conditions)
+  "Return non-nil if all CONDITIONS are logically equivalent.
+That is, they are either all nil, or all non-nil.  Arguments are
+evaluated in turn until one of them yields a logically different
+value; the remaining arguments are not evaluated.  If no argument
+yields nil, return the last argument's value."
+  (if (cdr conditions)
+      `(if ,(car conditions)
+           (and ,@(cdr conditions))
+         (not (or ,@(cdr conditions))))
+    `(or ,(car conditions) t)))
+
 (defmacro dolist (spec &rest body)
   "Loop over a list.
 Evaluate BODY with VAR bound to each car from LIST, in turn.
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0d5dc0e1c0..a96c1bfd23 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -1770,7 +1770,7 @@ diff-find-approx-text
 	(if (> (- (car forw) orig) (- orig (car back))) back forw)
       (or back forw))))
 
-(defsubst diff-xor (a b) (if a (if (not b) a) b))
+(define-obsolete-function-alias 'diff-xor 'xor "27.1")
 
 (defun diff-find-source-location (&optional other-file reverse noprompt)
   "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED).
@@ -1783,7 +1783,7 @@ diff-find-source-location
 SWITCHED is non-nil if the patch is already applied.
 NOPROMPT, if non-nil, means not to prompt the user."
   (save-excursion
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
 	   (char-offset (- (point) (diff-beginning-of-hunk t)))
            ;; Check that the hunk is well-formed.  Otherwise diff-mode and
            ;; the user may disagree on what constitutes the hunk
@@ -1909,7 +1909,7 @@ diff-apply-hunk
 	(insert (car new)))
       ;; Display BUF in a window
       (set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
-      (diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+      (diff-hunk-status-msg line-offset (xor switched reverse) nil)
       (when diff-advance-after-apply-hunk
 	(diff-hunk-next))))))
 
@@ -1921,7 +1921,7 @@ diff-test-hunk
   (pcase-let ((`(,buf ,line-offset ,pos ,src ,_dst ,switched)
                (diff-find-source-location nil reverse)))
     (set-window-point (display-buffer buf) (+ (car pos) (cdr src)))
-    (diff-hunk-status-msg line-offset (diff-xor reverse switched) t)))
+    (diff-hunk-status-msg line-offset (xor reverse switched) t)))
 
 
 (defun diff-kill-applied-hunks ()
@@ -1958,7 +1958,7 @@ diff-goto-source
       (pop-to-buffer buf)
       (goto-char (+ (car pos) (cdr src)))
       (when buffer (next-error-found buffer (current-buffer)))
-      (diff-hunk-status-msg line-offset (diff-xor reverse switched) t))))
+      (diff-hunk-status-msg line-offset (xor reverse switched) t))))
 
 
 (defun diff-current-defun ()
@@ -2253,7 +2253,7 @@ diff-delete-trailing-whitespace
   (interactive "P")
   (save-excursion
     (goto-char (point-min))
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
   	   (modified-buffers nil)
   	   (style (save-excursion
   	   	    (when (re-search-forward diff-hunk-header-re nil t)
diff --git a/lisp/windmove.el b/lisp/windmove.el
index ab47565dfa..f5f51480db 100644
--- a/lisp/windmove.el
+++ b/lisp/windmove.el
@@ -592,7 +592,7 @@ windmove-display-in-direction
 the prefix argument is reversed.
 When `switch-to-buffer-obey-display-actions' is non-nil,
 `switch-to-buffer' commands are also supported."
-  (let* ((no-select (not (eq (consp arg) windmove-display-no-select))) ; xor
+  (let* ((no-select (xor (consp arg) windmove-display-no-select))
          (old-window (or (minibuffer-selected-window) (selected-window)))
          (new-window)
          (minibuffer-depth (minibuffer-depth))
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index 06db8f5c90..f834c6a862 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -125,6 +125,28 @@ subr-test-when
   (should (equal (macroexpand-all '(when a b c d))
                  '(if a (progn b c d)))))
 
+(ert-deftest subr-test-xor ()
+  "Test `xor'."
+  (should-not (xor nil nil))
+  (should (eq (xor nil 'true) 'true))
+  (should (eq (xor 'true nil) 'true))
+  (should-not (xor t t)))
+
+(ert-deftest subr-test-equiv ()
+  "Test `equiv'."
+  (should (equiv))
+  (should (equiv nil))
+  (should (equiv nil nil))
+  (should (equiv nil nil nil))
+  (should (eq (equiv 'true) 'true))
+  (should (eq (equiv t 'true) 'true))
+  (let (x)
+    (should-not (equiv nil t (setq x t)))
+    (should-not (equiv t nil (setq x t)))
+    (should-not x)
+    (should (eq (equiv t t (setq x 'true)) 'true))
+    (should (eq x 'true))))
+
 (ert-deftest subr-test-version-parsing ()
   (should (equal (version-to-list ".5") '(0 5)))
   (should (equal (version-to-list "0.9 alpha1") '(0 9 -3 1)))
-- 
2.20.1


[-- Attachment #2: Type: text/plain, Size: 541 bytes --]


"Basil L. Contovounesios" <contovob@tcd.ie> writes:

> Oleh Krehel <oleh@oremacs.com> writes:
>
>> Should we figure out where to put the "main" `xor' and declare the rest as
>> obsolete aliases?
>
> Yes please!  I've had a patch to this effect sitting on a local branch
> for a while now.  Will touch it up and send soon.

Now attached.

It defines xor using defsubst instead of define-inline, so that it can
be included in subr.el.  Would defun be preferred instead?

It also includes Mattias' equiv suggestion.

WDYT?

Thanks,

-- 
Basil

^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 13:54             ` Andy Moreton
@ 2019-07-24 22:21               ` Basil L. Contovounesios
  0 siblings, 0 replies; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-24 22:21 UTC (permalink / raw)
  To: Andy Moreton; +Cc: emacs-devel

Andy Moreton <andrewjmoreton@gmail.com> writes:

> On Tue 23 Jul 2019, Basil L. Contovounesios wrote:
>
>> No, xor must evaluate all of its arguments in all n-ary interpretations
>> suggested so far:
>>
>> - Odd number of non-nil arguments
> Agreed.
>
>> - Exactly one non-nil argument
> After two non-nil arguments have been evaluated, there is no need to
> evaluate the remaining arguments.

Ah, right, sorry,

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-24 22:17   ` Basil L. Contovounesios
@ 2019-07-24 23:15     ` Stefan Monnier
  2019-07-24 23:44       ` Basil L. Contovounesios
  0 siblings, 1 reply; 51+ messages in thread
From: Stefan Monnier @ 2019-07-24 23:15 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: emacs-devel, Oleh Krehel

> It defines xor using defsubst instead of define-inline, so that it can
> be included in subr.el.

Great, thanks.

> Would defun be preferred instead?

No proeference here.

> It also includes Mattias' equiv suggestion.

Not convinced about this one:
- The name is too much like "equal/eq/eql": we need something much
  more explicit to avoid adding to the already existing confusion.
  I'd go with something like `bool-equal`.
- The fact that it's not a function is a definite downside.
- There's no use for it, currently.  If we can't find any use for it,
  I'm not sure it deserves to be in subr.el.
  Actual/potential uses of it might also help figure out if having it as
  a function would be beneficial/useful and whether the detail about
  which non-nil value is returned is important and whether supporting
  more than 2 args is important.


        Stefan




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-24 23:15     ` Stefan Monnier
@ 2019-07-24 23:44       ` Basil L. Contovounesios
  2019-07-25 12:07         ` Mattias Engdegård
  0 siblings, 1 reply; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-24 23:44 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel, Oleh Krehel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> It also includes Mattias' equiv suggestion.
>
> Not convinced about this one:
> - The name is too much like "equal/eq/eql": we need something much
>   more explicit to avoid adding to the already existing confusion.
>   I'd go with something like `bool-equal`.

Fine by me, but see my comment about xnor below.

> - The fact that it's not a function is a definite downside.

Sure, though I'm interested to hear arguments to the contrary.

> - There's no use for it, currently.  If we can't find any use for it,
>   I'm not sure it deserves to be in subr.el.
>   Actual/potential uses of it might also help figure out if having it as
>   a function would be beneficial/useful and whether the detail about
>   which non-nil value is returned is important and whether supporting
>   more than 2 args is important.

The patch uses equiv in two places.  In both cases it is equivalent to a
dyadic xnor function.  My original patch had provided dyadic xor and
xnor functions, so I'm happy either way.

Of course, we could also just provide a dyadic xor and use its negation
in place of the two equiv occurrences; then the xor parts of the patch
could be pushed sooner, and the relative merits of the equiv parts
discussed further, as you suggest.

Thanks,

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-24 23:44       ` Basil L. Contovounesios
@ 2019-07-25 12:07         ` Mattias Engdegård
  2019-07-25 17:28           ` Paul Eggert
  2019-07-28  7:09           ` Philippe Schnoebelen
  0 siblings, 2 replies; 51+ messages in thread
From: Mattias Engdegård @ 2019-07-25 12:07 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: Oleh Krehel, Stefan Monnier, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2237 bytes --]

25 juli 2019 kl. 01.44 skrev Basil L. Contovounesios <contovob@tcd.ie>:

> Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> - The name is too much like "equal/eq/eql": we need something much
>>  more explicit to avoid adding to the already existing confusion.
>>  I'd go with something like `bool-equal`.

bool-equal, bool-equiv, bool=, bool-eq are all fine as far as I'm concerned. `xnor' and `nxor', not so much.
Racket has `boolean=?', but presumably it only copes with #t/#f.
I'll be using `equiv' as placeholder below for brevity.

>> - The fact that it's not a function is a definite downside.
> 
> Sure, though I'm interested to hear arguments to the contrary.

Small catalogue of arguments:

For a dyadic function, defsubst, or define-inline: mirrors the other equivalence predicates. Simplicity. Can be used in HOFs. Most common case.

For a variadic macro: short-circuits, like `and' and `or'. Dramatically simplifies use with 3 or more arguments.

For macro and define-inline: Fast; no abstraction penalty. Since conditions are boolean expressions themselves, may benefit from bytecode optimisations.

Against macro and define-inline: static byte-code size increase if arguments are complex expressions, since some of them will be expanded twice.

For and against defsubst: compromise between space and size; slightly slower and sometimes smaller than a macro.

>> - There's no use for it, currently.  If we can't find any use for it,
>>  I'm not sure it deserves to be in subr.el.

It's hard to say how many times either of the operators would have been used in the past had they being known, since there are many ways of open-coding the logic depending on the circumstances. (Examples from a simple pattern search at the end.)

>>  Actual/potential uses of it might also help figure out if having it as
>>  a function would be beneficial/useful and whether the detail about
>>  which non-nil value is returned is important and whether supporting
>>  more than 2 args is important.

I see little point in having xor but not equiv, but agree with this argument.

Hydrazine on the fire: `implies' is occasionally useful, especially in assertions. (Dijkstra would have liked it.)


[-- Attachment #2: xor-equiv.log --]
[-- Type: application/octet-stream, Size: 2981 bytes --]

;; -*- compilation -*-
lisp/calc/calc-aent.el:806:35: boolean xor (eq-not)
lisp/calc/calc-alg.el:291:15: boolean equivalence (eq-not-not)
lisp/calc/calc-rewr.el:265:11: boolean xor (eq-not)
lisp/calc/calcalg2.el:3197:41: boolean xor (if-not)
lisp/calc/calcsel2.el:260:22: boolean xor (eq-not)
lisp/emacs-lisp/cl-seq.el:92:14: boolean xor (eq-not)
lisp/emacs-lisp/cl-seq.el:94:12: boolean xor (eq-not)
lisp/emacs-lisp/cl-seq.el:105:8: boolean xor (eq-not)
lisp/emacs-lisp/edebug.el:460:27: boolean equivalence (eq-not-not)
lisp/emacs-lisp/edebug.el:464:34: boolean xor (eq-not)
lisp/emacs-lisp/ert.el:492:19: boolean equivalence (eq-not-not)
lisp/eshell/em-ls.el:662:16: boolean xor (if-not)
lisp/gnus/gnus-group.el:1301:16: boolean xor (if-not)
lisp/gnus/gnus-sum.el:8471:19: boolean equivalence (if-not)
lisp/gnus/gnus-sum.el:8564:17: boolean xor (or-and-not-and-not)
lisp/gnus/gnus-sum.el:9111:11: boolean xor (if-not)
lisp/gnus/message.el:2296:15: boolean xor (or-and-not-and-not)
lisp/gnus/message.el:2385:34: boolean xor (or-and-not-and-not)
lisp/gnus/message.el:8201:5: boolean xor (if-not)
lisp/gnus/nnmail.el:1466:15: boolean xor (if-not)
lisp/mail/sendmail.el:1902:7: boolean xor (or-and-not-and-not)
lisp/net/browse-url.el:844:25: boolean xor (if-not)
lisp/net/eudc.el:875:17: boolean xor (or-and-not-and-not)
lisp/org/org.el:10073:3: boolean xor (if-not)
lisp/progmodes/cc-engine.el:10072:31: boolean equivalence (eq-not-not)
lisp/progmodes/cc-guess.el:504:25: boolean equivalence (or-and-not-or)
lisp/progmodes/cpp.el:329:26: boolean xor (eq-not)
lisp/progmodes/tcl.el:1348:20: boolean xor (if-not)
lisp/progmodes/tcl.el:1361:22: boolean xor (if-not)
lisp/progmodes/vhdl-mode.el:14421:17: boolean equivalence (or-and-and-not-not)
lisp/textmodes/bibtex.el:4107:22: boolean xor (if-not)
lisp/textmodes/bibtex.el:5284:10: boolean xor (if-not)
lisp/textmodes/rst.el:2021:18: boolean equivalence (or-and-and-not-not)
lisp/vc/cvs-status.el:452:29: boolean xor (if-not)
lisp/vc/ediff-util.el:2550:4: boolean xor (if-not)
lisp/vc/pcvs.el:1351:18: boolean xor (if-not)
lisp/vc/vc-bzr.el:286:34: boolean equivalence (if-not)
lisp/vc/vc-cvs.el:817:29: boolean equivalence (if-not)
lisp/array.el:679:9: boolean equivalence (or-and-and-not-not)
lisp/ffap.el:1548:12: boolean xor (if-not)
lisp/ffap.el:1924:12: boolean xor (if-not)
lisp/find-file.el:823:7: boolean xor (or-and-not-and-not)
lisp/forms.el:1777:9: boolean equivalence (or-and-and-not-not)
lisp/isearch.el:2662:29: boolean equivalence (eq-not-not)
lisp/locate.el:256:7: boolean xor (or-and-not-and-not)
lisp/locate.el:312:11: boolean xor (or-and-not-and-not)
lisp/ls-lisp.el:705:11: boolean equivalence (eq-not-not)
lisp/mouse.el:2596:15: boolean equivalence (if-not)
lisp/pcmpl-gnu.el:69:11: boolean equivalence (or-and-and-not-not)
lisp/pcmpl-gnu.el:94:11: boolean equivalence (or-and-and-not-not)
lisp/ps-print.el:5355:17: boolean xor (if-not)
test/src/regex-emacs-tests.el:177:9: boolean equivalence (or-and-not-or)

^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-25 12:07         ` Mattias Engdegård
@ 2019-07-25 17:28           ` Paul Eggert
  2019-07-25 17:46             ` Mattias Engdegård
  2019-07-28  7:09           ` Philippe Schnoebelen
  1 sibling, 1 reply; 51+ messages in thread
From: Paul Eggert @ 2019-07-25 17:28 UTC (permalink / raw)
  To: Mattias Engdegård, Basil L. Contovounesios
  Cc: emacs-devel, Stefan Monnier, Oleh Krehel

Mattias Engdegård wrote:
> there are many ways of open-coding the logic depending on the circumstances. (Examples from a simple pattern search at the end.)

Thanks for those examples: they form a compelling argument for adding both 
'equiv' and 'xor' (under whatever names we choose).



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-25 17:28           ` Paul Eggert
@ 2019-07-25 17:46             ` Mattias Engdegård
  0 siblings, 0 replies; 51+ messages in thread
From: Mattias Engdegård @ 2019-07-25 17:46 UTC (permalink / raw)
  To: Paul Eggert
  Cc: Basil L. Contovounesios, Oleh Krehel, Stefan Monnier, emacs-devel

25 juli 2019 kl. 19.28 skrev Paul Eggert <eggert@cs.ucla.edu>:
> 
> Mattias Engdegård wrote:
>> there are many ways of open-coding the logic depending on the circumstances. (Examples from a simple pattern search at the end.)
> 
> Thanks for those examples: they form a compelling argument for adding both 'equiv' and 'xor' (under whatever names we choose).

Just don't draw too many conclusions from them, as they reflect the search patterns used (or, fundamentally, my limited imagination).

It's not even sure the author would think of the logic as `equiv' or `xor' in every instance. For example (from a random package):

>	      (cl-assert (eq compilation-result
>			  (not (eq (car (last (car test))) 'vo)))

This is detected as an xor pattern, but it's really an assertion of the value of `compilation-result'.




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-23 10:44     ` Basil L. Contovounesios
  2019-07-23 11:01       ` Yuri Khan
@ 2019-07-25 21:46       ` Juri Linkov
  1 sibling, 0 replies; 51+ messages in thread
From: Juri Linkov @ 2019-07-25 21:46 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: Mattias Engdegård, Andy Moreton, emacs-devel

> That depends on the preferred semantics, right?  What semantics are you
> suggesting?  Judging from online discussions I've come across, there's
> more than one way to skin this cat.

Shouldn't xor have an associative property:

  (xor a b c) ≡ (xor a (xor b c)) ≡ (xor (xor a b) c)

> OTOH, a definition like the following:
>
>   (defsubst xor (cond1 cond2)
>     (cond ((not cond1) cond2)
>           ((not cond2) cond1)))
>
> not only performs simple logical xor more efficiently, but also returns
> whichever of the two arguments is non-nil (though I can't imagine when
> this last detail would ever be useful).

Returning a non-nil argument as-is is a useful property.
Most implementations lack this property, so better to use:

  (unless (and a b) (or a b))

But the implementation with 'cond' is nice too.

Another useful property is to evaluate arguments only once
that currently is not the case:

  (defvar a 0)
  (defvar b 0)
  (defmacro xor (a b) `(cond ((not ,a) ,b) ((not ,b) ,a)))
  (xor (setq a (1+ a)) nil)
  ⇒ 2

But I don't know if an implementation with such property
is possible at all (without using temporary variables).



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-25 12:07         ` Mattias Engdegård
  2019-07-25 17:28           ` Paul Eggert
@ 2019-07-28  7:09           ` Philippe Schnoebelen
  2019-07-28  8:04             ` Alan Mackenzie
  1 sibling, 1 reply; 51+ messages in thread
From: Philippe Schnoebelen @ 2019-07-28  7:09 UTC (permalink / raw)
  To: emacs-devel

On 2019/07/25 14:07, Mattias Engdegård wrote:
> 25 juli 2019 kl. 01.44 skrev Basil L. Contovounesios <contovob@tcd.ie>:
>
>
> bool-equal, bool-equiv, bool=, bool-eq are all fine as far as I'm concerned. `xnor' and `nxor', not so much.
> Racket has `boolean=?', but presumably it only copes with #t/#f.
> I'll be using `equiv' as placeholder below for brevity.

I like the name `iff'  for this function.


>>> - There's no use for it, currently.  If we can't find any use for it,
>>>  I'm not sure it deserves to be in subr.el.

I use the function a lot in ert tests, like in

      (should (iff (null somelist) (zerop (length somelist))))

--phs



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-28  7:09           ` Philippe Schnoebelen
@ 2019-07-28  8:04             ` Alan Mackenzie
  2019-07-28 19:43               ` Marcin Borkowski
  0 siblings, 1 reply; 51+ messages in thread
From: Alan Mackenzie @ 2019-07-28  8:04 UTC (permalink / raw)
  To: Philippe Schnoebelen; +Cc: emacs-devel

Hello, Philippe.

On Sun, Jul 28, 2019 at 09:09:01 +0200, Philippe Schnoebelen wrote:
> On 2019/07/25 14:07, Mattias Engdegård wrote:
> > 25 juli 2019 kl. 01.44 skrev Basil L. Contovounesios <contovob@tcd.ie>:


> > bool-equal, bool-equiv, bool=, bool-eq are all fine as far as I'm concerned. `xnor' and `nxor', not so much.
> > Racket has `boolean=?', but presumably it only copes with #t/#f.
> > I'll be using `equiv' as placeholder below for brevity.

> I like the name `iff'  for this function.

No, please don't use the name `iff' here.  In mathematical circles, iff
means "if and only if", and has done for many decades/several centuries.
Introducing it into Emacs with a radically different meaning will be
jarring in the extreme to anybody with a maths background.

> >>> - There's no use for it, currently.  If we can't find any use for it,
> >>>  I'm not sure it deserves to be in subr.el.

> I use the function a lot in ert tests, like in

>       (should (iff (null somelist) (zerop (length somelist))))

> --phs

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-28  8:04             ` Alan Mackenzie
@ 2019-07-28 19:43               ` Marcin Borkowski
  2019-07-30  9:36                 ` Alan Mackenzie
  0 siblings, 1 reply; 51+ messages in thread
From: Marcin Borkowski @ 2019-07-28 19:43 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: emacs-devel


On 2019-07-28, at 10:04, Alan Mackenzie <acm@muc.de> wrote:

> Hello, Philippe.
>
> On Sun, Jul 28, 2019 at 09:09:01 +0200, Philippe Schnoebelen wrote:
>> On 2019/07/25 14:07, Mattias Engdegård wrote:
>> > 25 juli 2019 kl. 01.44 skrev Basil L. Contovounesios <contovob@tcd.ie>:
>
>
>> > bool-equal, bool-equiv, bool=, bool-eq are all fine as far as I'm concerned. `xnor' and `nxor', not so much.
>> > Racket has `boolean=?', but presumably it only copes with #t/#f.
>> > I'll be using `equiv' as placeholder below for brevity.
>
>> I like the name `iff' for this function.
>
> No, please don't use the name `iff' here.  In mathematical circles, iff
> means "if and only if", and has done for many decades/several centuries.
> Introducing it into Emacs with a radically different meaning will be
> jarring in the extreme to anybody with a maths background.

Out of curiosity: how is that a "radically different meaning"?  I assume
that we are talking about a function `iff' such that
(iff nil nil) evaluates to t
(iff nil <non-nil>) evaluates to nil
(iff <non-nil> nil) evaluates to nil
(iff <non-nil> <non-nil>) evaluates to t (or perhaps the latter
<non-nil>)

This could of course be generalized to n arguments, though I'm not sure
whether anyone would want that (as with xor, there is more than one
"natural" way to do that).

If so, this is precisely the meaning we are talking about, no?

Also, Wikipedia claims that "iff" is relatively new (the fifties), btw.

Best,

--
Marcin Borkowski
http://mbork.pl



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-28 19:43               ` Marcin Borkowski
@ 2019-07-30  9:36                 ` Alan Mackenzie
  2019-07-30 10:57                   ` Philippe Schnoebelen
  2019-07-31 17:12                   ` Marcin Borkowski
  0 siblings, 2 replies; 51+ messages in thread
From: Alan Mackenzie @ 2019-07-30  9:36 UTC (permalink / raw)
  To: Marcin Borkowski; +Cc: emacs-devel

Hello, Marcin.

On Sun, Jul 28, 2019 at 21:43:19 +0200, Marcin Borkowski wrote:

> On 2019-07-28, at 10:04, Alan Mackenzie <acm@muc.de> wrote:

> > Hello, Philippe.

> > On Sun, Jul 28, 2019 at 09:09:01 +0200, Philippe Schnoebelen wrote:
> >> On 2019/07/25 14:07, Mattias Engdegård wrote:
> >> > 25 juli 2019 kl. 01.44 skrev Basil L. Contovounesios <contovob@tcd.ie>:


> >> > bool-equal, bool-equiv, bool=, bool-eq are all fine as far as I'm concerned. `xnor' and `nxor', not so much.
> >> > Racket has `boolean=?', but presumably it only copes with #t/#f.
> >> > I'll be using `equiv' as placeholder below for brevity.

> >> I like the name `iff' for this function.

> > No, please don't use the name `iff' here.  In mathematical circles, iff
> > means "if and only if", and has done for many decades/several centuries.
> > Introducing it into Emacs with a radically different meaning will be
> > jarring in the extreme to anybody with a maths background.

> Out of curiosity: how is that a "radically different meaning"?  I assume
> that we are talking about a function `iff' such that
> (iff nil nil) evaluates to t
> (iff nil <non-nil>) evaluates to nil
> (iff <non-nil> nil) evaluates to nil
> (iff <non-nil> <non-nil>) evaluates to t (or perhaps the latter
> <non-nil>)

Er, it's not radically different.  My brain seems to have been switched
off when I wrote my last post.  Apologies.

Less importantly, I don't like iff being used in this way.  I'm not sure
why.  Maybe it's because I've been used to iff applying solely to TRUE
and FALSE.  Maybe it's that I've been used to iff declaring a
proposition, rather than being something to be calculated.

> This could of course be generalized to n arguments, though I'm not sure
> whether anyone would want that (as with xor, there is more than one
> "natural" way to do that).

> If so, this is precisely the meaning we are talking about, no?

> Also, Wikipedia claims that "iff" is relatively new (the fifties), btw.

Well, I think that counts as "many decades", even if not "several
centuries".  It was certainly in widespread use in the 1970s.

> Best,

> --
> Marcin Borkowski
> http://mbork.pl

-- 
Alan Mackenzie (Nuremberg, Germany).



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-30  9:36                 ` Alan Mackenzie
@ 2019-07-30 10:57                   ` Philippe Schnoebelen
  2019-07-30 11:28                     ` Andy Moreton
  2019-07-31 17:12                   ` Marcin Borkowski
  1 sibling, 1 reply; 51+ messages in thread
From: Philippe Schnoebelen @ 2019-07-30 10:57 UTC (permalink / raw)
  To: emacs-devel

On 2019/07/30 11:36, Alan Mackenzie wrote:
> Less importantly, I don't like iff being used in this way.  I'm not sure
> why.  

Pros: (1) short (like and & xor)

        (2) exactly what we mean when writing assertions in code

           e.g.      (cl-assert (iff (listp arg) (listp res)))

            e.g.     (should (iff  (= b (max a b))   (<= a b)  ))

Cons: (1) name not known outside of mathematicians;

          (2) is an acronym

          (3) is not purely boolean, has order of evaluation, allows
side-effects (like and & or)

--Philippe



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-30 10:57                   ` Philippe Schnoebelen
@ 2019-07-30 11:28                     ` Andy Moreton
  2019-07-30 12:34                       ` Stefan Monnier
  0 siblings, 1 reply; 51+ messages in thread
From: Andy Moreton @ 2019-07-30 11:28 UTC (permalink / raw)
  To: emacs-devel

On Tue 30 Jul 2019, Philippe Schnoebelen wrote:

> On 2019/07/30 11:36, Alan Mackenzie wrote:
>> Less importantly, I don't like iff being used in this way.  I'm not sure
>> why.  
>
> Pros: (1) short (like and & xor)
>
>         (2) exactly what we mean when writing assertions in code
>
>            e.g.      (cl-assert (iff (listp arg) (listp res)))
>
>             e.g.     (should (iff  (= b (max a b))   (<= a b)  ))
>
> Cons: (1) name not known outside of mathematicians;
>
>           (2) is an acronym
>
>           (3) is not purely boolean, has order of evaluation, allows
> side-effects (like and & or)
>
> --Philippe

Please do not add `iff' as a symbol under any circumstance.

Short names must be universally understood by readers of the code with a
wide range of expertise and experience. A short name must also be needed
due to frequent use in typical code. This usage meets neither test.

    AndyM




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-30 11:28                     ` Andy Moreton
@ 2019-07-30 12:34                       ` Stefan Monnier
  2019-07-30 14:25                         ` Barry Fishman
  0 siblings, 1 reply; 51+ messages in thread
From: Stefan Monnier @ 2019-07-30 12:34 UTC (permalink / raw)
  To: Andy Moreton; +Cc: emacs-devel

> Please do not add `iff' as a symbol under any circumstance.
>
> Short names must be universally understood by readers of the code with a
> wide range of expertise and experience. A short name must also be needed
> due to frequent use in typical code. This usage meets neither test.

+1


        Stefan




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-30 12:34                       ` Stefan Monnier
@ 2019-07-30 14:25                         ` Barry Fishman
  2019-07-31  3:16                           ` Richard Stallman
  0 siblings, 1 reply; 51+ messages in thread
From: Barry Fishman @ 2019-07-30 14:25 UTC (permalink / raw)
  To: emacs-devel


On 2019-07-30 08:34:13 -04, Stefan Monnier wrote:
>> Please do not add `iff' as a symbol under any circumstance.
>>
>> Short names must be universally understood by readers of the code with a
>> wide range of expertise and experience. A short name must also be needed
>> due to frequent use in typical code. This usage meets neither test.
>
> +1

I find the iff and xor not obvious.  xor seems like it should be the
bitwise operation.   Why not something like bool-equal and
bool-not-equal being better at conveying exactly what is going on.

bool-eq and bool-ne is less clear, but shorter.

With bool= and bool/=, the latter suffers from the dual use of / in
symbols.

--
Barry Fishman




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-30 14:25                         ` Barry Fishman
@ 2019-07-31  3:16                           ` Richard Stallman
  2019-07-31 15:20                             ` Barry Fishman
  0 siblings, 1 reply; 51+ messages in thread
From: Richard Stallman @ 2019-07-31  3:16 UTC (permalink / raw)
  To: Barry Fishman; +Cc: emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  > I find the iff and xor not obvious.  xor seems like it should be the
  > bitwise operation.

That operation is called logxor.  It goes with logior and logand.

-- 
Dr Richard Stallman
President, Free Software Foundation (https://gnu.org, https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)





^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31  3:16                           ` Richard Stallman
@ 2019-07-31 15:20                             ` Barry Fishman
  2019-07-31 15:42                               ` Stefan Monnier
  2019-07-31 20:31                               ` Michael Heerdegen
  0 siblings, 2 replies; 51+ messages in thread
From: Barry Fishman @ 2019-07-31 15:20 UTC (permalink / raw)
  To: emacs-devel


On 2019-07-30 23:16:16 -04, Richard Stallman wrote:
> Me:
>   > I find the iff and xor not obvious.  xor seems like it should be the
>   > bitwise operation.
>
> That operation is called logxor.  It goes with logior and logand.

I can see keeping the Common Lisp names for things.  Emacs does that
with CAR and CDR.  I have concerns with just replacing one set of
confusing names with others that have differing semantics in other languages..

CL has AND, OR and NOT.  Like CLISP, Emacs has added XOR, but (I think)
with less useful semantics than CLISP's.

I question whether IFF is really that clear.  If you want to be
mathematical, just use <=> or ===, and can be complete by defining the
=> implementation operator also:

(defun <=> (x y) (not (xor x y)))

Or:
(defun => (x y) (or (not x) y)
(defun <=> (x y) (and (=> x y) (=> y x))

At least mathematicians will be happy (and nix programmers).

And possibly give XOR CLISP semantics: takes multiple arguments
returning NIL unless just one argument is true, in which case return
that argument.
-- 
Barry Fishman




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 15:20                             ` Barry Fishman
@ 2019-07-31 15:42                               ` Stefan Monnier
  2019-07-31 20:22                                 ` Basil L. Contovounesios
  2019-07-31 20:31                               ` Michael Heerdegen
  1 sibling, 1 reply; 51+ messages in thread
From: Stefan Monnier @ 2019-07-31 15:42 UTC (permalink / raw)
  To: Barry Fishman; +Cc: emacs-devel

I vote to define either `bool-equal` or `xor` (and this "or" is mutually
exclusive ;-), in either case taking exactly 2 arguments (while those
functions can be generalized to more, it's far from clear that it's
worth the trouble.  After all `equal` still only takes 2 args).

Then replace the various uses of <foo>-xor with calls to this
new function.


        Stefan




^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-30  9:36                 ` Alan Mackenzie
  2019-07-30 10:57                   ` Philippe Schnoebelen
@ 2019-07-31 17:12                   ` Marcin Borkowski
  1 sibling, 0 replies; 51+ messages in thread
From: Marcin Borkowski @ 2019-07-31 17:12 UTC (permalink / raw)
  To: Alan Mackenzie; +Cc: emacs-devel


On 2019-07-30, at 11:36, Alan Mackenzie <acm@muc.de> wrote:

> Hello, Marcin.
>
> On Sun, Jul 28, 2019 at 21:43:19 +0200, Marcin Borkowski wrote:
>
>> On 2019-07-28, at 10:04, Alan Mackenzie <acm@muc.de> wrote:
>
>> > Hello, Philippe.
>
>> > On Sun, Jul 28, 2019 at 09:09:01 +0200, Philippe Schnoebelen wrote:
>> >> On 2019/07/25 14:07, Mattias Engdegård wrote:
>> >> > 25 juli 2019 kl. 01.44 skrev Basil L. Contovounesios <contovob@tcd.ie>:
>
>
>> >> > bool-equal, bool-equiv, bool=, bool-eq are all fine as far as I'm concerned. `xnor' and `nxor', not so much.
>> >> > Racket has `boolean=?', but presumably it only copes with #t/#f.
>> >> > I'll be using `equiv' as placeholder below for brevity.
>
>> >> I like the name `iff' for this function.
>
>> > No, please don't use the name `iff' here.  In mathematical circles, iff
>> > means "if and only if", and has done for many decades/several centuries.
>> > Introducing it into Emacs with a radically different meaning will be
>> > jarring in the extreme to anybody with a maths background.
>
>> Out of curiosity: how is that a "radically different meaning"?  I assume
>> that we are talking about a function `iff' such that
>> (iff nil nil) evaluates to t
>> (iff nil <non-nil>) evaluates to nil
>> (iff <non-nil> nil) evaluates to nil
>> (iff <non-nil> <non-nil>) evaluates to t (or perhaps the latter
>> <non-nil>)
>
> Er, it's not radically different.  My brain seems to have been switched
> off when I wrote my last post.  Apologies.

No worry.

> Less importantly, I don't like iff being used in this way.  I'm not sure
> why.  Maybe it's because I've been used to iff applying solely to TRUE
> and FALSE.  Maybe it's that I've been used to iff declaring a
> proposition, rather than being something to be calculated.

Well, these are subjective points, but there are much stronger arguments
against `iff' raised by others...

Best,

-- 
Marcin Borkowski
http://mbork.pl



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 15:42                               ` Stefan Monnier
@ 2019-07-31 20:22                                 ` Basil L. Contovounesios
  2019-07-31 21:15                                   ` Michael Heerdegen
  2019-07-31 22:28                                   ` Mattias Engdegård
  0 siblings, 2 replies; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-31 20:22 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Barry Fishman, emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> I vote to define either `bool-equal` or `xor` (and this "or" is mutually
> exclusive ;-), in either case taking exactly 2 arguments (while those
> functions can be generalized to more, it's far from clear that it's
> worth the trouble.  After all `equal` still only takes 2 args).
>
> Then replace the various uses of <foo>-xor with calls to this
> new function.

+1.

If I had to pick one or the other, I would go with xor purely because it
is the one that has been copied several times.  But I am also perfectly
happy with bool-equal, especially if it is the historically/classically
preferred function.

Thanks,

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 15:20                             ` Barry Fishman
  2019-07-31 15:42                               ` Stefan Monnier
@ 2019-07-31 20:31                               ` Michael Heerdegen
  2019-07-31 21:38                                 ` Drew Adams
  1 sibling, 1 reply; 51+ messages in thread
From: Michael Heerdegen @ 2019-07-31 20:31 UTC (permalink / raw)
  To: Barry Fishman; +Cc: emacs-devel

Barry Fishman <barry@ecubist.org> writes:

> I question whether IFF is really that clear.  If you want to be
> mathematical, just use <=> or ===, and can be complete by defining the
> => implementation operator also:
>
> (defun <=> (x y) (not (xor x y)))
>
> Or:
> (defun => (x y) (or (not x) y)
> (defun <=> (x y) (and (=> x y) (=> y x))
>
> At least mathematicians will be happy (and nix programmers).

Even as a mathematician I would hate the name `iff' when using it in
Elisp which is not a language for writing mathematical stuff but for
writing code.  If you see something like

  (iff EXPR1 EXPR2)

in code, how does it read?  "If and only if evaluating EXPR1 yields
non-nil then - ehm ...??? eh - what? -- ah, ok, let's see, I remember,
it was just the negation of xor, so it actually means..." etc.
Terrible.

Michael.



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 20:22                                 ` Basil L. Contovounesios
@ 2019-07-31 21:15                                   ` Michael Heerdegen
  2019-07-31 22:28                                   ` Mattias Engdegård
  1 sibling, 0 replies; 51+ messages in thread
From: Michael Heerdegen @ 2019-07-31 21:15 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: Stefan Monnier, Barry Fishman, emacs-devel

"Basil L. Contovounesios" <contovob@tcd.ie> writes:

> If I had to pick one or the other, I would go with xor purely because it
> is the one that has been copied several times.  But I am also perfectly
> happy with bool-equal, especially if it is the historically/classically
> preferred function.

I also prefer xor.  bool-equal doesn't harmonize with the other
prominent equivalence predicates "eq" and "equal" because it sounds
similar to "equal" but is something totally different (actually "equal"
already is a weird name, but it's what we have).  OTOH, if you argue
that "equal" is a superset of "eq", and "bool-equal" a superset of
"equal" it would make some sense.

But "bool-equal" still makes me think about testing equality of some
data, while this is more about testing conditions, because the arguments
will most likely already be the result of some test, instead of some
data that has been constructed and we are inspecting.

Michael.



^ permalink raw reply	[flat|nested] 51+ messages in thread

* RE: 7 logical-xor implementations in source tree
  2019-07-31 20:31                               ` Michael Heerdegen
@ 2019-07-31 21:38                                 ` Drew Adams
  2019-07-31 22:03                                   ` Drew Adams
  2019-07-31 22:05                                   ` Basil L. Contovounesios
  0 siblings, 2 replies; 51+ messages in thread
From: Drew Adams @ 2019-07-31 21:38 UTC (permalink / raw)
  To: Michael Heerdegen, Barry Fishman; +Cc: emacs-devel

I don't understand what all the excitement
about defining an `xor' (with whatever name)
operator is about.

Common Lisp, like Emacs Lisp, has a bit-wise
xor operator.  That makes sense - useful.

And Common Lisp, like Emacs Lisp, does _not_
have an xor operator for arbitrary Lisp values.

https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node75.html

Why is it important that Emacs Lisp have such
an operator?  (Answer: it's not - YAGNI.)

Is it hard to understand (eq (not a) (not b))?
Is evaluation of that inefficient?  Is it too
verbose?

What's the motivation for all of this?

My vote's against adding any such operator.



^ permalink raw reply	[flat|nested] 51+ messages in thread

* RE: 7 logical-xor implementations in source tree
  2019-07-31 21:38                                 ` Drew Adams
@ 2019-07-31 22:03                                   ` Drew Adams
  2019-07-31 22:05                                   ` Basil L. Contovounesios
  1 sibling, 0 replies; 51+ messages in thread
From: Drew Adams @ 2019-07-31 22:03 UTC (permalink / raw)
  To: Michael Heerdegen, Barry Fishman; +Cc: emacs-devel

> Is it hard to understand (eq (not a) (not b))?

Well, someone off list pointed out that that is
(not (xor a b)); it is not (xor a b).  Yes, of
course.

I thought it'd be enough to speak to the general
thing under discussion, for which opposite-value
names such as "equiv", "iff", and `<=>' were
proposed.

But to be more pedantic/correct, is it hard to 
understand (not (eq (not a) (not b)))?
Or if you prefer, (eq (not a) (not (not b)))?

Is lack of an `xor' (or an `equiv') operator
a problem in practice?  How often have you felt
the need for it, really?

Why do you think Emacs Lisp, which was by design 
smaller than Common Lisp, needs such a function,
but Common Lisp does not?  What's the special
need we have for this?



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 21:38                                 ` Drew Adams
  2019-07-31 22:03                                   ` Drew Adams
@ 2019-07-31 22:05                                   ` Basil L. Contovounesios
  2019-07-31 23:20                                     ` Drew Adams
  1 sibling, 1 reply; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-31 22:05 UTC (permalink / raw)
  To: Drew Adams; +Cc: Michael Heerdegen, Barry Fishman, emacs-devel

Drew Adams <drew.adams@oracle.com> writes:

> I don't understand what all the excitement
> about defining an `xor' (with whatever name)
> operator is about.
>
> Common Lisp, like Emacs Lisp, has a bit-wise
> xor operator.  That makes sense - useful.
>
> And Common Lisp, like Emacs Lisp, does _not_
> have an xor operator for arbitrary Lisp values.
>
> https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node75.html
>
> Why is it important that Emacs Lisp have such
> an operator?  (Answer: it's not - YAGNI.)

If YAGNI, why are there currently 7 copies of this function in the Emacs
sources?

No-one said it's vitally important; it's just a minor nice-to-have.

> Is it hard to understand (eq (not a) (not b))?
> Is evaluation of that inefficient?  Is it too
> verbose?
>
> What's the motivation for all of this?

Same reason proper-list-p was added: reducing existing code duplication
and providing yet another convenience function for those that like it.

> My vote's against adding any such operator.

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 20:22                                 ` Basil L. Contovounesios
  2019-07-31 21:15                                   ` Michael Heerdegen
@ 2019-07-31 22:28                                   ` Mattias Engdegård
  2019-07-31 23:39                                     ` Basil L. Contovounesios
  1 sibling, 1 reply; 51+ messages in thread
From: Mattias Engdegård @ 2019-07-31 22:28 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: Stefan Monnier, Barry Fishman, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1661 bytes --]

31 juli 2019 kl. 22.22 skrev Basil L. Contovounesios <contovob@tcd.ie>:
> 
> Stefan Monnier <monnier@iro.umontreal.ca> writes:
> 
>> I vote to define either `bool-equal` or `xor` (and this "or" is mutually
>> exclusive ;-), in either case taking exactly 2 arguments (while those
>> functions can be generalized to more, it's far from clear that it's
>> worth the trouble.  After all `equal` still only takes 2 args).
>> 
>> Then replace the various uses of <foo>-xor with calls to this
>> new function.
> 
> +1.
> 
> If I had to pick one or the other, I would go with xor purely because it
> is the one that has been copied several times.  But I am also perfectly
> happy with bool-equal, especially if it is the historically/classically
> preferred function.

2 args is a reasonable simplification for both operators, and it allows `equiv' to be passed to HOFs that expect an equivalence relation. I haven't found much evidence for the need of an `equiv' of 3 or more operands either.

I'd favour `equiv' over `xor' but don't think we should be forced to make a hard choice: while each can be expressed as the inverse of the other, they are used with different intents ('exactly one' vs. 'same truth').

Regarding naming, I'd prefer equiv to bool-equiv to bool-equal, on the grounds that 'equivalence' is the most common way to describe the relation, well-known far outside specialised fields of logic and mathematics. `bool-equal' suggests either equality qua boolean or of booleanp values -- slightly ambiguous.

Attached is Basil's patch modified for a 2-arg `equiv' function, just as a reference point (and because code walks).


[-- Attachment #2: 0001-Add-conditional-operators-xor-and-equiv-to-subr.el.patch --]
[-- Type: application/octet-stream, Size: 20188 bytes --]

From 92d5420c202b72cd993cb0bb324473eb08082596 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Wed, 31 Jul 2019 19:45:06 +0200
Subject: [PATCH] Add conditional operators xor and equiv to subr.el

Suggested by Oleh Krehel and implemented by Basil Contovounesios in
the following thread:
https://lists.gnu.org/archive/html/emacs-devel/2019-07/msg00547.html

* lisp/array.el (xor): Move unused function from here...
* lisp/subr.el: ...to here.
(equiv): New function.
* lisp/gnus/spam.el (spam-xor):
* lisp/play/5x5.el (5x5-xor):
* lisp/proced.el (proced-xor):
* lisp/progmodes/idlwave.el (idlwave-xor):
* lisp/vc/diff-mode.el (diff-xor): Define as obsolete aliases of,
and replace all uses with, xor.
* lisp/jsonrpc.el: Remove unused dependency on array.el.
* lisp/org/org.el (org-xor): Move from here...
* lisp/org/org-compat.el (org-xor): ...to here, as a compatibility
shim for xor.
* lisp/progmodes/idlw-shell.el (idlwave-shell-enable-all-bp):
* lisp/simple.el (exchange-point-and-mark): Use equiv.
* lisp/strokes.el (strokes-xor): Remove commented-out xor
implementation.
* lisp/windmove.el (windmove-display-in-direction): Use xor.

* doc/lispref/control.texi (Control Structures): Extend menu entry
for new combining conditions.
(Combining Conditions):
* etc/NEWS (Lisp Changes): Document xor and equiv.

* test/lisp/subr-tests.el (subr-test-xor, subr-test-equiv): New
tests.
---
 doc/lispref/control.texi     | 39 +++++++++++++++++++++++++++++++++---
 etc/NEWS                     | 11 ++++++++++
 lisp/array.el                |  5 -----
 lisp/gnus/spam.el            |  6 ++----
 lisp/jsonrpc.el              |  3 +--
 lisp/org/org-compat.el       |  8 ++++++++
 lisp/org/org.el              |  4 ----
 lisp/play/5x5.el             |  8 +++-----
 lisp/proced.el               | 11 ++++------
 lisp/progmodes/idlw-shell.el |  2 +-
 lisp/progmodes/idlwave.el    | 25 +++++++++++------------
 lisp/simple.el               |  3 +--
 lisp/strokes.el              |  6 ------
 lisp/subr.el                 | 15 ++++++++++++++
 lisp/vc/diff-mode.el         | 12 +++++------
 lisp/windmove.el             |  2 +-
 test/lisp/subr-tests.el      | 14 +++++++++++++
 17 files changed, 115 insertions(+), 59 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index e98daf66e9..311178ec49 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -38,7 +38,7 @@ Control Structures
 @menu
 * Sequencing::             Evaluation in textual order.
 * Conditionals::           @code{if}, @code{cond}, @code{when}, @code{unless}.
-* Combining Conditions::   @code{and}, @code{or}, @code{not}.
+* Combining Conditions::   @code{and}, @code{or}, @code{not}, and friends.
 * Pattern-Matching Conditional::  How to use @code{pcase} and friends.
 * Iteration::              @code{while} loops.
 * Generators::             Generic sequences and coroutines.
@@ -298,8 +298,8 @@ Combining Conditions
 @section Constructs for Combining Conditions
 @cindex combining conditions
 
-  This section describes three constructs that are often used together
-with @code{if} and @code{cond} to express complicated conditions.  The
+  This section describes constructs that are often used together with
+@code{if} and @code{cond} to express complicated conditions.  The
 constructs @code{and} and @code{or} can also be used individually as
 kinds of multiple conditional constructs.
 
@@ -419,6 +419,39 @@ Combining Conditions
 @var{arg3})} never evaluates any argument more than once.
 @end defspec
 
+@defun equiv condition1 condition2
+The @code{equiv} macro tests whether @var{condition1} and
+@var{condition2} are logically equivalent, i.e., either both
+@code{nil} or both non-@code{nil}.
+
+If both arguments are non-@code{nil}, then the @code{equiv} call
+returns the value of @var{condition2}.  If both arguments are
+@code{nil}, @code{equiv} returns @code{t}.
+
+For example, the following expression tests whether either some state
+is enabled (@var{enabled} is non-@code{nil}) and should be disabled
+(@var{disable} is also non-@code{nil}), or the state is disabled
+(@var{enabled} is @code{nil}) and should be enabled (@var{disable} is
+also @code{nil}); if either of these conditions holds, the state
+should subsequently be toggled:
+
+@example
+(when (equiv enabled disable)
+  ;; Toggle state
+  @dots{})
+@end example
+@end defun
+
+@defun xor condition1 condition2
+This function returns the boolean exclusive-or of @var{condition1} and
+@var{condition2}.  That is, @code{xor} returns @code{nil} if either
+both arguments are @code{nil}, or both are non-@code{nil}.  Otherwise,
+it returns the value of that argument which is non-@code{nil}.
+
+In other words, @code{xor} and @code{equiv} are the logical inverses
+of each other.
+@end defun
+
 @node Pattern-Matching Conditional
 @section Pattern-Matching Conditional
 @cindex pcase
diff --git a/etc/NEWS b/etc/NEWS
index 7dfb08256f..20390c9bfd 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2480,6 +2480,17 @@ parameter to control descending into subdirectories, and a
 FOLLOW-SYMLINK parameter to say that symbolic links that point to
 other directories should be followed.
 
++++
+** New function 'xor' returns the boolean exclusive-or if its args.
+The function was previously defined in array.el, but has been moved to
+subr.el so that it is available by default.  Several duplicates of
+'xor' in other packages are now obsolete aliases of 'xor'.
+
++++
+** New function 'equiv' tests whether its args are logically equivalent.
+If its arguments are both non-nil, 'equiv' returns the value of the
+last one; if they are both nil, it returns t; otherwise, it returns nil.
+
 \f
 * Changes in Emacs 27.1 on Non-Free Operating Systems
 
diff --git a/lisp/array.el b/lisp/array.el
index 2fffe0197e..965e97ff55 100644
--- a/lisp/array.el
+++ b/lisp/array.el
@@ -740,11 +740,6 @@ limit-index
 	((> index limit) limit)
 	(t index)))
 
-(defun xor (pred1 pred2)
-  "Return the logical exclusive or of predicates PRED1 and PRED2."
-  (and (or pred1 pred2)
-       (not (and pred1 pred2))))
-
 (defun current-line ()
   "Return the current buffer line at point.  The first line is 0."
   (count-lines (point-min) (line-beginning-position)))
diff --git a/lisp/gnus/spam.el b/lisp/gnus/spam.el
index d752bf0efe..f990e0cba1 100644
--- a/lisp/gnus/spam.el
+++ b/lisp/gnus/spam.el
@@ -708,9 +708,7 @@ spam-clear-cache
   "Clear the `spam-caches' entry for a check."
   (remhash symbol spam-caches))
 
-(defun spam-xor (a b)
-  "Logical A xor B."
-  (and (or a b) (not (and a b))))
+(define-obsolete-function-alias 'spam-xor 'xor "27.1")
 
 (defun spam-set-difference (list1 list2)
   "Return a set difference of LIST1 and LIST2.
@@ -2550,7 +2548,7 @@ spam-spamoracle-learn
         (goto-char (point-min))
         (dolist (article articles)
           (insert (spam-get-article-as-string article)))
-        (let* ((arg (if (spam-xor unregister article-is-spam-p)
+        (let* ((arg (if (xor unregister article-is-spam-p)
                         "-spam"
                       "-good"))
                (status
diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
index 0fffee6866..329cc5561f 100644
--- a/lisp/jsonrpc.el
+++ b/lisp/jsonrpc.el
@@ -43,9 +43,8 @@
 (require 'warnings)
 (require 'pcase)
 (require 'ert) ; to escape a `condition-case-unless-debug'
-(require 'array) ; xor
 
-\f
+
 ;;; Public API
 ;;;
 
diff --git a/lisp/org/org-compat.el b/lisp/org/org-compat.el
index 062bb4c5ca..bb927fedf9 100644
--- a/lisp/org/org-compat.el
+++ b/lisp/org/org-compat.el
@@ -362,6 +362,14 @@ 'org-texinfo-def-table-markup
 \f
 ;;; Miscellaneous functions
 
+;; `xor' was added in Emacs 27.1.
+(defalias 'org-xor
+  (if (fboundp 'xor)
+      #'xor
+    (lambda (a b)
+      "Exclusive or."
+      (if a (not b) b))))
+
 (defun org-version-check (version feature level)
   (let* ((v1 (mapcar 'string-to-number (split-string version "[.]")))
          (v2 (mapcar 'string-to-number (split-string emacs-version "[.]")))
diff --git a/lisp/org/org.el b/lisp/org/org.el
index 5aa49b29d6..79725ac752 100644
--- a/lisp/org/org.el
+++ b/lisp/org/org.el
@@ -10068,10 +10068,6 @@ org-link-unescape-single-byte-sequence
 	       (char-to-string (string-to-number byte 16)))
 	     (cdr (split-string hex "%")) ""))
 
-(defun org-xor (a b)
-  "Exclusive or."
-  (if a (not b) b))
-
 (defun org-fixup-message-id-for-http (s)
   "Replace special characters in a message id, so it can be used in an http query."
   (when (string-match "%" s)
diff --git a/lisp/play/5x5.el b/lisp/play/5x5.el
index 28748cc351..c5d4659123 100644
--- a/lisp/play/5x5.el
+++ b/lisp/play/5x5.el
@@ -435,8 +435,8 @@ 5x5-make-xor-with-mutation
     (dotimes (y 5x5-grid-size)
       (dotimes (x 5x5-grid-size)
         (5x5-set-cell xored y x
-                      (5x5-xor (5x5-cell current y x)
-                               (5x5-cell best    y x)))))
+                      (xor (5x5-cell current y x)
+                           (5x5-cell best    y x)))))
     (5x5-mutate-solution xored)))
 
 (defun 5x5-mutate-solution (solution)
@@ -931,9 +931,7 @@ 5x5-randomize
 
 ;; Support functions
 
-(defun 5x5-xor (x y)
-  "Boolean exclusive-or of X and Y."
-  (and (or x y) (not (and x y))))
+(define-obsolete-function-alias '5x5-xor 'xor "27.1")
 
 (defun 5x5-y-or-n-p (prompt)
   "5x5 wrapper for `y-or-n-p' which respects the `5x5-hassle-me' setting."
diff --git a/lisp/proced.el b/lisp/proced.el
index 5f35fa34a0..fe687a4f83 100644
--- a/lisp/proced.el
+++ b/lisp/proced.el
@@ -1194,10 +1194,7 @@ proced-time-lessp
 
 ;;; Sorting
 
-(defsubst proced-xor (b1 b2)
-  "Return the logical exclusive or of args B1 and B2."
-  (and (or b1 b2)
-       (not (and b1 b2))))
+(define-obsolete-function-alias 'proced-xor 'xor "27.1")
 
 (defun proced-sort-p (p1 p2)
   "Predicate for sorting processes P1 and P2."
@@ -1208,8 +1205,8 @@ proced-sort-p
              (k2 (cdr (assq (car sorter) (cdr p2)))))
         ;; if the attributes are undefined, we should really abort sorting
         (if (and k1 k2)
-            (proced-xor (funcall (nth 1 sorter) k1 k2)
-                        (nth 2 sorter))))
+            (xor (funcall (nth 1 sorter) k1 k2)
+                 (nth 2 sorter))))
     (let ((sort-list proced-sort-internal) sorter predicate k1 k2)
       (catch 'done
         (while (setq sorter (pop sort-list))
@@ -1219,7 +1216,7 @@ proced-sort-p
                 (if (and k1 k2)
                     (funcall (nth 1 sorter) k1 k2)))
           (if (not (eq predicate 'equal))
-              (throw 'done (proced-xor predicate (nth 2 sorter)))))
+              (throw 'done (xor predicate (nth 2 sorter)))))
         (eq t predicate)))))
 
 (defun proced-sort (process-alist sorter descend)
diff --git a/lisp/progmodes/idlw-shell.el b/lisp/progmodes/idlw-shell.el
index 3bd99620d0..568479c822 100644
--- a/lisp/progmodes/idlw-shell.el
+++ b/lisp/progmodes/idlw-shell.el
@@ -2604,7 +2604,7 @@ idlwave-shell-enable-all-bp
   (let  ((bpl (or bpl idlwave-shell-bp-alist)) disabled modified)
     (while bpl
       (setq disabled (idlwave-shell-bp-get (car bpl) 'disabled))
-      (when (idlwave-xor (not disabled) (eq enable 'enable))
+      (when (equiv disabled (eq enable 'enable))
 	(idlwave-shell-toggle-enable-current-bp
 	 (car bpl) (if (eq enable 'enable) 'enable 'disable) no-update)
 	(push (car bpl) modified))
diff --git a/lisp/progmodes/idlwave.el b/lisp/progmodes/idlwave.el
index 614d73e23b..1b4b55c94f 100644
--- a/lisp/progmodes/idlwave.el
+++ b/lisp/progmodes/idlwave.el
@@ -8813,9 +8813,8 @@ idlwave-study-twins
 
 ;; FIXME: Dynamically scoped vars need to use the `idlwave-' prefix.
 ;; (defvar type)
-(defmacro idlwave-xor (a b)
-  `(and (or ,a ,b)
-	(not (and ,a ,b))))
+
+(define-obsolete-function-alias 'idlwave-xor 'xor "27.1")
 
 (defun idlwave-routine-entry-compare (a b)
   "Compare two routine info entries for sorting.
@@ -8919,17 +8918,17 @@ idlwave-routine-twin-compare
     ;; Now: follow JD's ideas about sorting.  Looks really simple now,
     ;; doesn't it?  The difficult stuff is hidden above...
     (cond
-     ((idlwave-xor asysp  bsysp)       asysp)	; System entries first
-     ((idlwave-xor aunresp bunresp)    bunresp) ; Unresolved last
+     ((xor asysp   bsysp)     asysp)        ; System entries first
+     ((xor aunresp bunresp)   bunresp)      ; Unresolved last
      ((and idlwave-sort-prefer-buffer-info
-	   (idlwave-xor abufp bbufp))  abufp)	; Buffers before non-buffers
-     ((idlwave-xor acompp bcompp)      acompp)	; Compiled entries
-     ((idlwave-xor apathp bpathp)      apathp)	; Library before non-library
-     ((idlwave-xor anamep bnamep)      anamep)	; Correct file names first
-     ((and idlwave-twin-class anamep bnamep     ; both file names match ->
-	   (idlwave-xor adefp bdefp))  bdefp)	; __define after __method
-     ((> anpath bnpath)                t)	; Who is first on path?
-     (t                                nil))))	; Default
+           (xor abufp bbufp)) abufp)        ; Buffers before non-buffers
+     ((xor acompp bcompp)     acompp)       ; Compiled entries
+     ((xor apathp bpathp)     apathp)       ; Library before non-library
+     ((xor anamep bnamep)     anamep)       ; Correct file names first
+     ((and idlwave-twin-class anamep bnamep ; both file names match ->
+           (xor adefp bdefp)) bdefp)        ; __define after __method
+     ((> anpath bnpath)       t)            ; Who is first on path?
+     (t                       nil))))       ; Default
 
 (defun idlwave-routine-source-file (source)
   (if (nth 2 source)
diff --git a/lisp/simple.el b/lisp/simple.el
index 0bc39f08c0..40a0e69adb 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -5855,8 +5855,7 @@ exchange-point-and-mark
     (goto-char omark)
     (cond (temp-highlight
 	   (setq-local transient-mark-mode (cons 'only transient-mark-mode)))
-	  ((or (and arg (region-active-p)) ; (xor arg (not (region-active-p)))
-	       (not (or arg (region-active-p))))
+          ((equiv arg (region-active-p))
 	   (deactivate-mark))
 	  (t (activate-mark)))
     nil))
diff --git a/lisp/strokes.el b/lisp/strokes.el
index 0c671c43ac..6edf58c7b6 100644
--- a/lisp/strokes.el
+++ b/lisp/strokes.el
@@ -1524,12 +1524,6 @@ strokes-xpm-char-bit-p
   (or (eq char ?\s)
       (eq char ?*)))
 
-;;(defsubst strokes-xor (a b)  ### Should I make this an inline function? ###
-;;  "T if one and only one of A and B is non-nil; otherwise, returns nil.
-;;NOTE: Don't use this as a numeric xor since it treats all non-nil
-;;      values as t including `0' (zero)."
-;;  (eq (null a) (not (null b))))
-
 (defsubst strokes-xpm-encode-length-as-string (length)
   "Given some LENGTH in [0,62) do a fast lookup of its encoding."
   (aref strokes-base64-chars length))
diff --git a/lisp/subr.el b/lisp/subr.el
index eea4e045dd..336f45b093 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -209,6 +209,21 @@ unless
   (declare (indent 1) (debug t))
   (cons 'if (cons cond (cons nil body))))
 
+(defsubst xor (cond1 cond2)
+  "Return the boolean exclusive-or of COND1 and COND2.
+If only one of the arguments is non-nil, return it; otherwise
+return nil."
+  (declare (pure t) (side-effect-free error-free))
+  (cond ((not cond1) cond2)
+        ((not cond2) cond1)))
+
+(defsubst equiv (cond1 cond2)
+  "Return non-nil if COND1 and COND2 are logically equivalent.
+That is, they are either both nil, or both non-nil.
+If neither argument is nil, returns COND2."
+  (declare (pure t) (side-effect-free error-free))
+  (if cond1 cond2 (not cond2)))
+
 (defmacro dolist (spec &rest body)
   "Loop over a list.
 Evaluate BODY with VAR bound to each car from LIST, in turn.
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0d5dc0e1c0..a96c1bfd23 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -1770,7 +1770,7 @@ diff-find-approx-text
 	(if (> (- (car forw) orig) (- orig (car back))) back forw)
       (or back forw))))
 
-(defsubst diff-xor (a b) (if a (if (not b) a) b))
+(define-obsolete-function-alias 'diff-xor 'xor "27.1")
 
 (defun diff-find-source-location (&optional other-file reverse noprompt)
   "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED).
@@ -1783,7 +1783,7 @@ diff-find-source-location
 SWITCHED is non-nil if the patch is already applied.
 NOPROMPT, if non-nil, means not to prompt the user."
   (save-excursion
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
 	   (char-offset (- (point) (diff-beginning-of-hunk t)))
            ;; Check that the hunk is well-formed.  Otherwise diff-mode and
            ;; the user may disagree on what constitutes the hunk
@@ -1909,7 +1909,7 @@ diff-apply-hunk
 	(insert (car new)))
       ;; Display BUF in a window
       (set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
-      (diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+      (diff-hunk-status-msg line-offset (xor switched reverse) nil)
       (when diff-advance-after-apply-hunk
 	(diff-hunk-next))))))
 
@@ -1921,7 +1921,7 @@ diff-test-hunk
   (pcase-let ((`(,buf ,line-offset ,pos ,src ,_dst ,switched)
                (diff-find-source-location nil reverse)))
     (set-window-point (display-buffer buf) (+ (car pos) (cdr src)))
-    (diff-hunk-status-msg line-offset (diff-xor reverse switched) t)))
+    (diff-hunk-status-msg line-offset (xor reverse switched) t)))
 
 
 (defun diff-kill-applied-hunks ()
@@ -1958,7 +1958,7 @@ diff-goto-source
       (pop-to-buffer buf)
       (goto-char (+ (car pos) (cdr src)))
       (when buffer (next-error-found buffer (current-buffer)))
-      (diff-hunk-status-msg line-offset (diff-xor reverse switched) t))))
+      (diff-hunk-status-msg line-offset (xor reverse switched) t))))
 
 
 (defun diff-current-defun ()
@@ -2253,7 +2253,7 @@ diff-delete-trailing-whitespace
   (interactive "P")
   (save-excursion
     (goto-char (point-min))
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
   	   (modified-buffers nil)
   	   (style (save-excursion
   	   	    (when (re-search-forward diff-hunk-header-re nil t)
diff --git a/lisp/windmove.el b/lisp/windmove.el
index ab47565dfa..f5f51480db 100644
--- a/lisp/windmove.el
+++ b/lisp/windmove.el
@@ -592,7 +592,7 @@ windmove-display-in-direction
 the prefix argument is reversed.
 When `switch-to-buffer-obey-display-actions' is non-nil,
 `switch-to-buffer' commands are also supported."
-  (let* ((no-select (not (eq (consp arg) windmove-display-no-select))) ; xor
+  (let* ((no-select (xor (consp arg) windmove-display-no-select))
          (old-window (or (minibuffer-selected-window) (selected-window)))
          (new-window)
          (minibuffer-depth (minibuffer-depth))
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index 0023680738..4e4671e849 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -125,6 +125,20 @@ 'subr-tests--parent-mode
   (should (equal (macroexpand-all '(when a b c d))
                  '(if a (progn b c d)))))
 
+(ert-deftest subr-test-xor ()
+  "Test `xor'."
+  (should-not (xor nil nil))
+  (should (eq (xor nil 'true) 'true))
+  (should (eq (xor 'true nil) 'true))
+  (should-not (xor t t)))
+
+(ert-deftest subr-test-equiv ()
+  "Test `equiv'."
+  (should (equal (equiv nil nil) t))
+  (should (equal (equiv 3 'a) 'a))
+  (should-not (equiv 'b nil))
+  (should-not (equiv nil 'c)))
+
 (ert-deftest subr-test-version-parsing ()
   (should (equal (version-to-list ".5") '(0 5)))
   (should (equal (version-to-list "0.9 alpha1") '(0 9 -3 1)))
-- 
2.20.1 (Apple Git-117)


^ permalink raw reply related	[flat|nested] 51+ messages in thread

* RE: 7 logical-xor implementations in source tree
  2019-07-31 22:05                                   ` Basil L. Contovounesios
@ 2019-07-31 23:20                                     ` Drew Adams
  2019-08-01  0:09                                       ` Basil L. Contovounesios
  0 siblings, 1 reply; 51+ messages in thread
From: Drew Adams @ 2019-07-31 23:20 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: Michael Heerdegen, Barry Fishman, emacs-devel

> > Why is it important that Emacs Lisp have such
> > an operator?  (Answer: it's not - YAGNI.)
> 
> If YAGNI, why are there currently 7 copies of this function in the
> Emacs sources?

Dunno.  I probably wouldn't have added them.

OK, so we have 7 definitions of a trivial function.
How many occurrences of those 7 functions?  Do their
uses really benefit from defining such a function?
(How about just replacing all of them by sexps?)

> No-one said it's vitally important; it's just a minor nice-to-have.

Just 1 opinion: too minor and not nice enough to have.

> > Is it hard to understand (eq (not a) (not b))?
> > Is evaluation of that inefficient?  Is it too
> > verbose?  What's the motivation for all of this?
> 
> Same reason proper-list-p was added: reducing existing code duplication
> and providing yet another convenience function for those that like it.

I see.  You added `proper-list-p' also.  Also missing
from Common Lisp, FWIW.

How much real code duplication did adding that predicate
eliminate?  At least in that case the code replaced is,
even if straightforward and not very verbose, not
completely trivial.

I don't object strongly to adding such functions.
But I'd expect a convenience function to typically be
more convenient - replace more than a few occurrences
of more complex code.



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 22:28                                   ` Mattias Engdegård
@ 2019-07-31 23:39                                     ` Basil L. Contovounesios
  2019-08-02 10:29                                       ` Mattias Engdegård
  0 siblings, 1 reply; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-07-31 23:39 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: Stefan Monnier, Barry Fishman, emacs-devel

Mattias Engdegård <mattiase@acm.org> writes:

> Attached is Basil's patch modified for a 2-arg `equiv' function, just as a
> reference point (and because code walks).

Thanks, LGTM except for some minor nits:

> +@defun equiv condition1 condition2
> +The @code{equiv} macro tests whether @var{condition1} and
                    ^^^^^
No longer a macro.

> +@var{condition2} are logically equivalent, i.e., either both
> +@code{nil} or both non-@code{nil}.
> +
> +If both arguments are non-@code{nil}, then the @code{equiv} call
> +returns the value of @var{condition2}.  If both arguments are
> +@code{nil}, @code{equiv} returns @code{t}.

What about when one is nil and the other isn't?

> +For example, the following expression tests whether either some state
> +is enabled (@var{enabled} is non-@code{nil}) and should be disabled
> +(@var{disable} is also non-@code{nil}), or the state is disabled
> +(@var{enabled} is @code{nil}) and should be enabled (@var{disable} is
> +also @code{nil}); if either of these conditions holds, the state
> +should subsequently be toggled:
> +
> +@example
> +(when (equiv enabled disable)
> +  ;; Toggle state
> +  @dots{})
> +@end example
> +@end defun

[I would love to see a better and less forced example than this, BTW.]

> +@defun xor condition1 condition2
> +This function returns the boolean exclusive-or of @var{condition1} and
> +@var{condition2}.  That is, @code{xor} returns @code{nil} if either
> +both arguments are @code{nil}, or both are non-@code{nil}.  Otherwise,
> +it returns the value of that argument which is non-@code{nil}.
> +
> +In other words, @code{xor} and @code{equiv} are the logical inverses
> +of each other.

Should each of these two definitions refer to the other, e.g. as "Note
that this is the logical inverse of..."?

> --- a/lisp/jsonrpc.el
> +++ b/lisp/jsonrpc.el
> @@ -43,9 +43,8 @@
>  (require 'warnings)
>  (require 'pcase)
>  (require 'ert) ; to escape a `condition-case-unless-debug'
> -(require 'array) ; xor
>  
> -\f
> +
>  ;;; Public API
>  ;;;

Is the whitespace change preferred?

> +(defsubst equiv (cond1 cond2)
> +  "Return non-nil if COND1 and COND2 are logically equivalent.
> +That is, they are either both nil, or both non-nil.
> +If neither argument is nil, returns COND2."

I think "return COND2" reads better in this context.

Thanks,

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 23:20                                     ` Drew Adams
@ 2019-08-01  0:09                                       ` Basil L. Contovounesios
  0 siblings, 0 replies; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-08-01  0:09 UTC (permalink / raw)
  To: Drew Adams; +Cc: Michael Heerdegen, Barry Fishman, emacs-devel

Drew Adams <drew.adams@oracle.com> writes:

>> > Why is it important that Emacs Lisp have such
>> > an operator?  (Answer: it's not - YAGNI.)
>> 
>> If YAGNI, why are there currently 7 copies of this function in the
>> Emacs sources?
>
> Dunno.  I probably wouldn't have added them.

Then we're not talking about the same Y in YAGNI. ;)

> OK, so we have 7 definitions of a trivial function.
> How many occurrences of those 7 functions?

You can see all 17 in-tree occurrences in the patch(es) proposed
elsewhere in this thread.  There exist also further copies and
occurrences in some GNU ELPA and MELPA packages.

> Do their uses really benefit from defining such a function?

I think they allow the relevant code to remain more succinct and clear.

> (How about just replacing all of them by sexps?)

I invite you to try it, but I don't expect the diff or result to be that
pretty, especially not in idlwave.el.

>> No-one said it's vitally important; it's just a minor nice-to-have.
>
> Just 1 opinion: too minor and not nice enough to have.

I think the OP, as well as most follow-ups in this thread, indicate the
opposite.

>> > Is it hard to understand (eq (not a) (not b))?
>> > Is evaluation of that inefficient?  Is it too
>> > verbose?  What's the motivation for all of this?
>> 
>> Same reason proper-list-p was added: reducing existing code duplication
>> and providing yet another convenience function for those that like it.
>
> I see.  You added `proper-list-p' also.  Also missing
> from Common Lisp, FWIW.
>
> How much real code duplication did adding that predicate
> eliminate?  At least in that case the code replaced is,
> even if straightforward and not very verbose, not
> completely trivial.
>
> I don't object strongly to adding such functions.
> But I'd expect a convenience function to typically be
> more convenient - replace more than a few occurrences
> of more complex code.

The point is not just eliminating existing duplication, but also
providing a named convenience function for future use (both naming and
convenience are useful in general).  You may not see the utility in this
case (which is perfectly understandable) but it is clear to me that
several others do.  (See also the recent discussion on decoded time
accessors.)

Thanks,

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-07-31 23:39                                     ` Basil L. Contovounesios
@ 2019-08-02 10:29                                       ` Mattias Engdegård
  2019-08-02 10:59                                         ` Basil L. Contovounesios
  0 siblings, 1 reply; 51+ messages in thread
From: Mattias Engdegård @ 2019-08-02 10:29 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: Stefan Monnier, Barry Fishman, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 743 bytes --]

1 aug. 2019 kl. 01.39 skrev Basil L. Contovounesios <contovob@tcd.ie>:
> 
> Thanks, LGTM except for some minor nits:

Thank you! I've fixed them all according to your suggestions, clarified a few more things here and there (mentioning the slight change of semantics of `xor' in NEWS), and attached the revised patch.

However, given that the naming and desirability of `equiv' are (amicably) disputed, I thought it prudent to prepare a reduced patch with `xor' only, as an incremental step if nothing else.

>> +(when (equiv enabled disable)
>> +  ;; Toggle state
>> +  @dots{})
> 
> [I would love to see a better and less forced example than this, BTW.]

Replaced with another example -- perhaps no better; you be the judge.


[-- Attachment #2: 0001-Add-conditional-operators-xor-and-equiv-to-subr.el.patch --]
[-- Type: application/octet-stream, Size: 20067 bytes --]

From 853438bb0705383f8be1439f492afe02d6219f3c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Wed, 31 Jul 2019 19:45:06 +0200
Subject: [PATCH] Add conditional operators xor and equiv to subr.el

Suggested by Oleh Krehel and implemented by Basil Contovounesios in
the following thread:
https://lists.gnu.org/archive/html/emacs-devel/2019-07/msg00547.html

* lisp/array.el (xor): Move unused function from here...
* lisp/subr.el: ...to here.
(equiv): New function.
* lisp/gnus/spam.el (spam-xor):
* lisp/play/5x5.el (5x5-xor):
* lisp/proced.el (proced-xor):
* lisp/progmodes/idlwave.el (idlwave-xor):
* lisp/vc/diff-mode.el (diff-xor): Define as obsolete aliases of,
and replace all uses with, xor.
* lisp/jsonrpc.el: Remove unused dependency on array.el.
* lisp/org/org.el (org-xor): Move from here...
* lisp/org/org-compat.el (org-xor): ...to here, as a compatibility
shim for xor.
* lisp/progmodes/idlw-shell.el (idlwave-shell-enable-all-bp):
* lisp/simple.el (exchange-point-and-mark): Use equiv.
* lisp/strokes.el (strokes-xor): Remove commented-out xor
implementation.
* lisp/windmove.el (windmove-display-in-direction): Use xor.

* doc/lispref/control.texi (Control Structures): Extend menu entry
for new combining conditions.
(Combining Conditions):
* etc/NEWS (Lisp Changes): Document xor and equiv.

* test/lisp/subr-tests.el (subr-test-xor, subr-test-equiv): New
tests.
---
 doc/lispref/control.texi     | 36 +++++++++++++++++++++++++++++++++---
 etc/NEWS                     | 11 +++++++++++
 lisp/array.el                |  5 -----
 lisp/gnus/spam.el            |  6 ++----
 lisp/jsonrpc.el              |  1 -
 lisp/org/org-compat.el       |  8 ++++++++
 lisp/org/org.el              |  4 ----
 lisp/play/5x5.el             |  8 +++-----
 lisp/proced.el               | 11 ++++-------
 lisp/progmodes/idlw-shell.el |  2 +-
 lisp/progmodes/idlwave.el    | 25 ++++++++++++-------------
 lisp/simple.el               |  3 +--
 lisp/strokes.el              |  6 ------
 lisp/subr.el                 | 15 +++++++++++++++
 lisp/vc/diff-mode.el         | 12 ++++++------
 lisp/windmove.el             |  2 +-
 test/lisp/subr-tests.el      | 14 ++++++++++++++
 17 files changed, 111 insertions(+), 58 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index e98daf66e9..87ef52adbf 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -38,7 +38,7 @@ Control Structures
 @menu
 * Sequencing::             Evaluation in textual order.
 * Conditionals::           @code{if}, @code{cond}, @code{when}, @code{unless}.
-* Combining Conditions::   @code{and}, @code{or}, @code{not}.
+* Combining Conditions::   @code{and}, @code{or}, @code{not}, and friends.
 * Pattern-Matching Conditional::  How to use @code{pcase} and friends.
 * Iteration::              @code{while} loops.
 * Generators::             Generic sequences and coroutines.
@@ -298,8 +298,8 @@ Combining Conditions
 @section Constructs for Combining Conditions
 @cindex combining conditions
 
-  This section describes three constructs that are often used together
-with @code{if} and @code{cond} to express complicated conditions.  The
+  This section describes constructs that are often used together with
+@code{if} and @code{cond} to express complicated conditions.  The
 constructs @code{and} and @code{or} can also be used individually as
 kinds of multiple conditional constructs.
 
@@ -419,6 +419,36 @@ Combining Conditions
 @var{arg3})} never evaluates any argument more than once.
 @end defspec
 
+@defun equiv condition1 condition2
+The @code{equiv} function tests whether @var{condition1} and
+@var{condition2} are logically equivalent, i.e., either both
+@code{nil} or both non-@code{nil}.
+
+If both arguments are non-@code{nil}, then @code{equiv} returns
+@var{condition2}.  If both arguments are @code{nil}, @code{equiv}
+returns @code{t}.  Otherwise, it returns @code{nil}.
+
+For example, the following expression adds a value, @var{x}, to a list
+if the value is a number and @var{want-number} is true, or if it is
+something else and @var{want-number} is false.
+
+@example
+(when (equiv (numberp x) want-number)
+  (add-to-list 'collection x))
+@end example
+
+Note that @code{equiv} is the logical inverse of @code{xor} below.
+@end defun
+
+@defun xor condition1 condition2
+This function returns the boolean exclusive-or of @var{condition1} and
+@var{condition2}.  That is, @code{xor} returns @code{nil} if either
+both arguments are @code{nil}, or both are non-@code{nil}.  Otherwise,
+it returns the value of that argument which is non-@code{nil}.
+
+Note that @code{xor} is the logical inverse of @code{equiv} above.
+@end defun
+
 @node Pattern-Matching Conditional
 @section Pattern-Matching Conditional
 @cindex pcase
diff --git a/etc/NEWS b/etc/NEWS
index 486e677539..0b6f8ccb7a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2480,6 +2480,17 @@ parameter to control descending into subdirectories, and a
 FOLLOW-SYMLINK parameter to say that symbolic links that point to
 other directories should be followed.
 
++++
+** New function 'xor' returns the boolean exclusive-or if its args.
+The function was previously defined in array.el, but has been moved to
+subr.el so that it is available by default.  Several duplicates of
+'xor' in other packages are now obsolete aliases of 'xor'.
+
++++
+** New function 'equiv' tests whether its args are logically equivalent.
+If its arguments are both non-nil, 'equiv' returns the value of the
+last one; if they are both nil, it returns t; otherwise, it returns nil.
+
 \f
 * Changes in Emacs 27.1 on Non-Free Operating Systems
 
diff --git a/lisp/array.el b/lisp/array.el
index 2fffe0197e..965e97ff55 100644
--- a/lisp/array.el
+++ b/lisp/array.el
@@ -740,11 +740,6 @@ limit-index
 	((> index limit) limit)
 	(t index)))
 
-(defun xor (pred1 pred2)
-  "Return the logical exclusive or of predicates PRED1 and PRED2."
-  (and (or pred1 pred2)
-       (not (and pred1 pred2))))
-
 (defun current-line ()
   "Return the current buffer line at point.  The first line is 0."
   (count-lines (point-min) (line-beginning-position)))
diff --git a/lisp/gnus/spam.el b/lisp/gnus/spam.el
index d752bf0efe..f990e0cba1 100644
--- a/lisp/gnus/spam.el
+++ b/lisp/gnus/spam.el
@@ -708,9 +708,7 @@ spam-clear-cache
   "Clear the `spam-caches' entry for a check."
   (remhash symbol spam-caches))
 
-(defun spam-xor (a b)
-  "Logical A xor B."
-  (and (or a b) (not (and a b))))
+(define-obsolete-function-alias 'spam-xor 'xor "27.1")
 
 (defun spam-set-difference (list1 list2)
   "Return a set difference of LIST1 and LIST2.
@@ -2550,7 +2548,7 @@ spam-spamoracle-learn
         (goto-char (point-min))
         (dolist (article articles)
           (insert (spam-get-article-as-string article)))
-        (let* ((arg (if (spam-xor unregister article-is-spam-p)
+        (let* ((arg (if (xor unregister article-is-spam-p)
                         "-spam"
                       "-good"))
                (status
diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
index 0fffee6866..85fd40ecd2 100644
--- a/lisp/jsonrpc.el
+++ b/lisp/jsonrpc.el
@@ -43,7 +43,6 @@
 (require 'warnings)
 (require 'pcase)
 (require 'ert) ; to escape a `condition-case-unless-debug'
-(require 'array) ; xor
 
 \f
 ;;; Public API
diff --git a/lisp/org/org-compat.el b/lisp/org/org-compat.el
index 062bb4c5ca..bb927fedf9 100644
--- a/lisp/org/org-compat.el
+++ b/lisp/org/org-compat.el
@@ -362,6 +362,14 @@ 'org-texinfo-def-table-markup
 \f
 ;;; Miscellaneous functions
 
+;; `xor' was added in Emacs 27.1.
+(defalias 'org-xor
+  (if (fboundp 'xor)
+      #'xor
+    (lambda (a b)
+      "Exclusive or."
+      (if a (not b) b))))
+
 (defun org-version-check (version feature level)
   (let* ((v1 (mapcar 'string-to-number (split-string version "[.]")))
          (v2 (mapcar 'string-to-number (split-string emacs-version "[.]")))
diff --git a/lisp/org/org.el b/lisp/org/org.el
index 5aa49b29d6..79725ac752 100644
--- a/lisp/org/org.el
+++ b/lisp/org/org.el
@@ -10068,10 +10068,6 @@ org-link-unescape-single-byte-sequence
 	       (char-to-string (string-to-number byte 16)))
 	     (cdr (split-string hex "%")) ""))
 
-(defun org-xor (a b)
-  "Exclusive or."
-  (if a (not b) b))
-
 (defun org-fixup-message-id-for-http (s)
   "Replace special characters in a message id, so it can be used in an http query."
   (when (string-match "%" s)
diff --git a/lisp/play/5x5.el b/lisp/play/5x5.el
index 28748cc351..c5d4659123 100644
--- a/lisp/play/5x5.el
+++ b/lisp/play/5x5.el
@@ -435,8 +435,8 @@ 5x5-make-xor-with-mutation
     (dotimes (y 5x5-grid-size)
       (dotimes (x 5x5-grid-size)
         (5x5-set-cell xored y x
-                      (5x5-xor (5x5-cell current y x)
-                               (5x5-cell best    y x)))))
+                      (xor (5x5-cell current y x)
+                           (5x5-cell best    y x)))))
     (5x5-mutate-solution xored)))
 
 (defun 5x5-mutate-solution (solution)
@@ -931,9 +931,7 @@ 5x5-randomize
 
 ;; Support functions
 
-(defun 5x5-xor (x y)
-  "Boolean exclusive-or of X and Y."
-  (and (or x y) (not (and x y))))
+(define-obsolete-function-alias '5x5-xor 'xor "27.1")
 
 (defun 5x5-y-or-n-p (prompt)
   "5x5 wrapper for `y-or-n-p' which respects the `5x5-hassle-me' setting."
diff --git a/lisp/proced.el b/lisp/proced.el
index 5f35fa34a0..fe687a4f83 100644
--- a/lisp/proced.el
+++ b/lisp/proced.el
@@ -1194,10 +1194,7 @@ proced-time-lessp
 
 ;;; Sorting
 
-(defsubst proced-xor (b1 b2)
-  "Return the logical exclusive or of args B1 and B2."
-  (and (or b1 b2)
-       (not (and b1 b2))))
+(define-obsolete-function-alias 'proced-xor 'xor "27.1")
 
 (defun proced-sort-p (p1 p2)
   "Predicate for sorting processes P1 and P2."
@@ -1208,8 +1205,8 @@ proced-sort-p
              (k2 (cdr (assq (car sorter) (cdr p2)))))
         ;; if the attributes are undefined, we should really abort sorting
         (if (and k1 k2)
-            (proced-xor (funcall (nth 1 sorter) k1 k2)
-                        (nth 2 sorter))))
+            (xor (funcall (nth 1 sorter) k1 k2)
+                 (nth 2 sorter))))
     (let ((sort-list proced-sort-internal) sorter predicate k1 k2)
       (catch 'done
         (while (setq sorter (pop sort-list))
@@ -1219,7 +1216,7 @@ proced-sort-p
                 (if (and k1 k2)
                     (funcall (nth 1 sorter) k1 k2)))
           (if (not (eq predicate 'equal))
-              (throw 'done (proced-xor predicate (nth 2 sorter)))))
+              (throw 'done (xor predicate (nth 2 sorter)))))
         (eq t predicate)))))
 
 (defun proced-sort (process-alist sorter descend)
diff --git a/lisp/progmodes/idlw-shell.el b/lisp/progmodes/idlw-shell.el
index 188ec012cf..355d67f91c 100644
--- a/lisp/progmodes/idlw-shell.el
+++ b/lisp/progmodes/idlw-shell.el
@@ -2604,7 +2604,7 @@ idlwave-shell-enable-all-bp
   (let  ((bpl (or bpl idlwave-shell-bp-alist)) disabled modified)
     (while bpl
       (setq disabled (idlwave-shell-bp-get (car bpl) 'disabled))
-      (when (idlwave-xor (not disabled) (eq enable 'enable))
+      (when (equiv disabled (eq enable 'enable))
 	(idlwave-shell-toggle-enable-current-bp
 	 (car bpl) (if (eq enable 'enable) 'enable 'disable) no-update)
 	(push (car bpl) modified))
diff --git a/lisp/progmodes/idlwave.el b/lisp/progmodes/idlwave.el
index 614d73e23b..1b4b55c94f 100644
--- a/lisp/progmodes/idlwave.el
+++ b/lisp/progmodes/idlwave.el
@@ -8813,9 +8813,8 @@ idlwave-study-twins
 
 ;; FIXME: Dynamically scoped vars need to use the `idlwave-' prefix.
 ;; (defvar type)
-(defmacro idlwave-xor (a b)
-  `(and (or ,a ,b)
-	(not (and ,a ,b))))
+
+(define-obsolete-function-alias 'idlwave-xor 'xor "27.1")
 
 (defun idlwave-routine-entry-compare (a b)
   "Compare two routine info entries for sorting.
@@ -8919,17 +8918,17 @@ idlwave-routine-twin-compare
     ;; Now: follow JD's ideas about sorting.  Looks really simple now,
     ;; doesn't it?  The difficult stuff is hidden above...
     (cond
-     ((idlwave-xor asysp  bsysp)       asysp)	; System entries first
-     ((idlwave-xor aunresp bunresp)    bunresp) ; Unresolved last
+     ((xor asysp   bsysp)     asysp)        ; System entries first
+     ((xor aunresp bunresp)   bunresp)      ; Unresolved last
      ((and idlwave-sort-prefer-buffer-info
-	   (idlwave-xor abufp bbufp))  abufp)	; Buffers before non-buffers
-     ((idlwave-xor acompp bcompp)      acompp)	; Compiled entries
-     ((idlwave-xor apathp bpathp)      apathp)	; Library before non-library
-     ((idlwave-xor anamep bnamep)      anamep)	; Correct file names first
-     ((and idlwave-twin-class anamep bnamep     ; both file names match ->
-	   (idlwave-xor adefp bdefp))  bdefp)	; __define after __method
-     ((> anpath bnpath)                t)	; Who is first on path?
-     (t                                nil))))	; Default
+           (xor abufp bbufp)) abufp)        ; Buffers before non-buffers
+     ((xor acompp bcompp)     acompp)       ; Compiled entries
+     ((xor apathp bpathp)     apathp)       ; Library before non-library
+     ((xor anamep bnamep)     anamep)       ; Correct file names first
+     ((and idlwave-twin-class anamep bnamep ; both file names match ->
+           (xor adefp bdefp)) bdefp)        ; __define after __method
+     ((> anpath bnpath)       t)            ; Who is first on path?
+     (t                       nil))))       ; Default
 
 (defun idlwave-routine-source-file (source)
   (if (nth 2 source)
diff --git a/lisp/simple.el b/lisp/simple.el
index 08021ce0e0..4283a1d69b 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -5855,8 +5855,7 @@ exchange-point-and-mark
     (goto-char omark)
     (cond (temp-highlight
 	   (setq-local transient-mark-mode (cons 'only transient-mark-mode)))
-	  ((or (and arg (region-active-p)) ; (xor arg (not (region-active-p)))
-	       (not (or arg (region-active-p))))
+          ((equiv arg (region-active-p))
 	   (deactivate-mark))
 	  (t (activate-mark)))
     nil))
diff --git a/lisp/strokes.el b/lisp/strokes.el
index 0c671c43ac..6edf58c7b6 100644
--- a/lisp/strokes.el
+++ b/lisp/strokes.el
@@ -1524,12 +1524,6 @@ strokes-xpm-char-bit-p
   (or (eq char ?\s)
       (eq char ?*)))
 
-;;(defsubst strokes-xor (a b)  ### Should I make this an inline function? ###
-;;  "T if one and only one of A and B is non-nil; otherwise, returns nil.
-;;NOTE: Don't use this as a numeric xor since it treats all non-nil
-;;      values as t including `0' (zero)."
-;;  (eq (null a) (not (null b))))
-
 (defsubst strokes-xpm-encode-length-as-string (length)
   "Given some LENGTH in [0,62) do a fast lookup of its encoding."
   (aref strokes-base64-chars length))
diff --git a/lisp/subr.el b/lisp/subr.el
index eea4e045dd..913d1f95b6 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -209,6 +209,21 @@ unless
   (declare (indent 1) (debug t))
   (cons 'if (cons cond (cons nil body))))
 
+(defsubst xor (cond1 cond2)
+  "Return the boolean exclusive-or of COND1 and COND2.
+If only one of the arguments is non-nil, return it; otherwise
+return nil."
+  (declare (pure t) (side-effect-free error-free))
+  (cond ((not cond1) cond2)
+        ((not cond2) cond1)))
+
+(defsubst equiv (cond1 cond2)
+  "Return non-nil if COND1 and COND2 are logically equivalent.
+That is, they are either both nil, or both non-nil.
+If neither argument is nil, return COND2."
+  (declare (pure t) (side-effect-free error-free))
+  (if cond1 cond2 (not cond2)))
+
 (defmacro dolist (spec &rest body)
   "Loop over a list.
 Evaluate BODY with VAR bound to each car from LIST, in turn.
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0d5dc0e1c0..a96c1bfd23 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -1770,7 +1770,7 @@ diff-find-approx-text
 	(if (> (- (car forw) orig) (- orig (car back))) back forw)
       (or back forw))))
 
-(defsubst diff-xor (a b) (if a (if (not b) a) b))
+(define-obsolete-function-alias 'diff-xor 'xor "27.1")
 
 (defun diff-find-source-location (&optional other-file reverse noprompt)
   "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED).
@@ -1783,7 +1783,7 @@ diff-find-source-location
 SWITCHED is non-nil if the patch is already applied.
 NOPROMPT, if non-nil, means not to prompt the user."
   (save-excursion
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
 	   (char-offset (- (point) (diff-beginning-of-hunk t)))
            ;; Check that the hunk is well-formed.  Otherwise diff-mode and
            ;; the user may disagree on what constitutes the hunk
@@ -1909,7 +1909,7 @@ diff-apply-hunk
 	(insert (car new)))
       ;; Display BUF in a window
       (set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
-      (diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+      (diff-hunk-status-msg line-offset (xor switched reverse) nil)
       (when diff-advance-after-apply-hunk
 	(diff-hunk-next))))))
 
@@ -1921,7 +1921,7 @@ diff-test-hunk
   (pcase-let ((`(,buf ,line-offset ,pos ,src ,_dst ,switched)
                (diff-find-source-location nil reverse)))
     (set-window-point (display-buffer buf) (+ (car pos) (cdr src)))
-    (diff-hunk-status-msg line-offset (diff-xor reverse switched) t)))
+    (diff-hunk-status-msg line-offset (xor reverse switched) t)))
 
 
 (defun diff-kill-applied-hunks ()
@@ -1958,7 +1958,7 @@ diff-goto-source
       (pop-to-buffer buf)
       (goto-char (+ (car pos) (cdr src)))
       (when buffer (next-error-found buffer (current-buffer)))
-      (diff-hunk-status-msg line-offset (diff-xor reverse switched) t))))
+      (diff-hunk-status-msg line-offset (xor reverse switched) t))))
 
 
 (defun diff-current-defun ()
@@ -2253,7 +2253,7 @@ diff-delete-trailing-whitespace
   (interactive "P")
   (save-excursion
     (goto-char (point-min))
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
   	   (modified-buffers nil)
   	   (style (save-excursion
   	   	    (when (re-search-forward diff-hunk-header-re nil t)
diff --git a/lisp/windmove.el b/lisp/windmove.el
index ab47565dfa..f5f51480db 100644
--- a/lisp/windmove.el
+++ b/lisp/windmove.el
@@ -592,7 +592,7 @@ windmove-display-in-direction
 the prefix argument is reversed.
 When `switch-to-buffer-obey-display-actions' is non-nil,
 `switch-to-buffer' commands are also supported."
-  (let* ((no-select (not (eq (consp arg) windmove-display-no-select))) ; xor
+  (let* ((no-select (xor (consp arg) windmove-display-no-select))
          (old-window (or (minibuffer-selected-window) (selected-window)))
          (new-window)
          (minibuffer-depth (minibuffer-depth))
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index 0023680738..4e4671e849 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -125,6 +125,20 @@ 'subr-tests--parent-mode
   (should (equal (macroexpand-all '(when a b c d))
                  '(if a (progn b c d)))))
 
+(ert-deftest subr-test-xor ()
+  "Test `xor'."
+  (should-not (xor nil nil))
+  (should (eq (xor nil 'true) 'true))
+  (should (eq (xor 'true nil) 'true))
+  (should-not (xor t t)))
+
+(ert-deftest subr-test-equiv ()
+  "Test `equiv'."
+  (should (equal (equiv nil nil) t))
+  (should (equal (equiv 3 'a) 'a))
+  (should-not (equiv 'b nil))
+  (should-not (equiv nil 'c)))
+
 (ert-deftest subr-test-version-parsing ()
   (should (equal (version-to-list ".5") '(0 5)))
   (should (equal (version-to-list "0.9 alpha1") '(0 9 -3 1)))
-- 
2.20.1 (Apple Git-117)


[-- Attachment #3: 0001-Add-conditional-operator-xor-to-subr.el.patch --]
[-- Type: application/octet-stream, Size: 18549 bytes --]

From 26a22e339b71654ed417385aa27514175c67275e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Wed, 31 Jul 2019 19:45:06 +0200
Subject: [PATCH] Add conditional operator xor to subr.el

Suggested by Oleh Krehel and implemented by Basil Contovounesios in
the following thread:
https://lists.gnu.org/archive/html/emacs-devel/2019-07/msg00547.html

* lisp/array.el (xor): Move unused function from here...
* lisp/subr.el: ...to here, and improve.
* lisp/gnus/spam.el (spam-xor):
* lisp/play/5x5.el (5x5-xor):
* lisp/proced.el (proced-xor):
* lisp/progmodes/idlwave.el (idlwave-xor):
* lisp/vc/diff-mode.el (diff-xor): Define as obsolete aliases of,
and replace all uses with, xor.
* lisp/jsonrpc.el: Remove unused dependency on array.el.
* lisp/org/org.el (org-xor): Move from here...
* lisp/org/org-compat.el (org-xor): ...to here, as a compatibility
shim for xor.
* lisp/progmodes/idlw-shell.el (idlwave-shell-enable-all-bp):
* lisp/simple.el (exchange-point-and-mark):
* lisp/windmove.el (windmove-display-in-direction): Use xor.
* lisp/strokes.el (strokes-xor): Remove commented-out xor
implementation.

* doc/lispref/control.texi (Control Structures): Extend menu entry
for new combining condition.
(Combining Conditions):
* etc/NEWS (Lisp Changes): Document xor.

* test/lisp/subr-tests.el (subr-test-xor): New test.
---
 doc/lispref/control.texi     | 15 ++++++++++++---
 etc/NEWS                     |  7 +++++++
 lisp/array.el                |  5 -----
 lisp/gnus/spam.el            |  6 ++----
 lisp/jsonrpc.el              |  1 -
 lisp/org/org-compat.el       |  8 ++++++++
 lisp/org/org.el              |  4 ----
 lisp/play/5x5.el             |  8 +++-----
 lisp/proced.el               | 11 ++++-------
 lisp/progmodes/idlw-shell.el |  2 +-
 lisp/progmodes/idlwave.el    | 25 ++++++++++++-------------
 lisp/simple.el               |  3 +--
 lisp/strokes.el              |  6 ------
 lisp/subr.el                 |  8 ++++++++
 lisp/vc/diff-mode.el         | 12 ++++++------
 lisp/windmove.el             |  2 +-
 test/lisp/subr-tests.el      |  7 +++++++
 17 files changed, 72 insertions(+), 58 deletions(-)

diff --git a/doc/lispref/control.texi b/doc/lispref/control.texi
index e98daf66e9..31948fa079 100644
--- a/doc/lispref/control.texi
+++ b/doc/lispref/control.texi
@@ -38,7 +38,7 @@ Control Structures
 @menu
 * Sequencing::             Evaluation in textual order.
 * Conditionals::           @code{if}, @code{cond}, @code{when}, @code{unless}.
-* Combining Conditions::   @code{and}, @code{or}, @code{not}.
+* Combining Conditions::   @code{and}, @code{or}, @code{not}, and friends.
 * Pattern-Matching Conditional::  How to use @code{pcase} and friends.
 * Iteration::              @code{while} loops.
 * Generators::             Generic sequences and coroutines.
@@ -298,8 +298,8 @@ Combining Conditions
 @section Constructs for Combining Conditions
 @cindex combining conditions
 
-  This section describes three constructs that are often used together
-with @code{if} and @code{cond} to express complicated conditions.  The
+  This section describes constructs that are often used together with
+@code{if} and @code{cond} to express complicated conditions.  The
 constructs @code{and} and @code{or} can also be used individually as
 kinds of multiple conditional constructs.
 
@@ -419,6 +419,15 @@ Combining Conditions
 @var{arg3})} never evaluates any argument more than once.
 @end defspec
 
+@defun xor condition1 condition2
+This function returns the boolean exclusive-or of @var{condition1} and
+@var{condition2}.  That is, @code{xor} returns @code{nil} if either
+both arguments are @code{nil}, or both are non-@code{nil}.  Otherwise,
+it returns the value of that argument which is non-@code{nil}.
+
+Note that in contrast to @code{or}, both arguments are always evaluated.
+@end defun
+
 @node Pattern-Matching Conditional
 @section Pattern-Matching Conditional
 @cindex pcase
diff --git a/etc/NEWS b/etc/NEWS
index 486e677539..c23feefbc3 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2480,6 +2480,13 @@ parameter to control descending into subdirectories, and a
 FOLLOW-SYMLINK parameter to say that symbolic links that point to
 other directories should be followed.
 
++++
+** New function 'xor' returns the boolean exclusive-or if its args.
+The function was previously defined in array.el, but has been moved to
+subr.el so that it is available by default.  It now always returns the
+non-nil argument when the other is nil.  Several duplicates of 'xor'
+in other packages are now obsolete aliases of 'xor'.
+
 \f
 * Changes in Emacs 27.1 on Non-Free Operating Systems
 
diff --git a/lisp/array.el b/lisp/array.el
index 2fffe0197e..965e97ff55 100644
--- a/lisp/array.el
+++ b/lisp/array.el
@@ -740,11 +740,6 @@ limit-index
 	((> index limit) limit)
 	(t index)))
 
-(defun xor (pred1 pred2)
-  "Return the logical exclusive or of predicates PRED1 and PRED2."
-  (and (or pred1 pred2)
-       (not (and pred1 pred2))))
-
 (defun current-line ()
   "Return the current buffer line at point.  The first line is 0."
   (count-lines (point-min) (line-beginning-position)))
diff --git a/lisp/gnus/spam.el b/lisp/gnus/spam.el
index d752bf0efe..f990e0cba1 100644
--- a/lisp/gnus/spam.el
+++ b/lisp/gnus/spam.el
@@ -708,9 +708,7 @@ spam-clear-cache
   "Clear the `spam-caches' entry for a check."
   (remhash symbol spam-caches))
 
-(defun spam-xor (a b)
-  "Logical A xor B."
-  (and (or a b) (not (and a b))))
+(define-obsolete-function-alias 'spam-xor 'xor "27.1")
 
 (defun spam-set-difference (list1 list2)
   "Return a set difference of LIST1 and LIST2.
@@ -2550,7 +2548,7 @@ spam-spamoracle-learn
         (goto-char (point-min))
         (dolist (article articles)
           (insert (spam-get-article-as-string article)))
-        (let* ((arg (if (spam-xor unregister article-is-spam-p)
+        (let* ((arg (if (xor unregister article-is-spam-p)
                         "-spam"
                       "-good"))
                (status
diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el
index 0fffee6866..85fd40ecd2 100644
--- a/lisp/jsonrpc.el
+++ b/lisp/jsonrpc.el
@@ -43,7 +43,6 @@
 (require 'warnings)
 (require 'pcase)
 (require 'ert) ; to escape a `condition-case-unless-debug'
-(require 'array) ; xor
 
 \f
 ;;; Public API
diff --git a/lisp/org/org-compat.el b/lisp/org/org-compat.el
index 062bb4c5ca..bb927fedf9 100644
--- a/lisp/org/org-compat.el
+++ b/lisp/org/org-compat.el
@@ -362,6 +362,14 @@ 'org-texinfo-def-table-markup
 \f
 ;;; Miscellaneous functions
 
+;; `xor' was added in Emacs 27.1.
+(defalias 'org-xor
+  (if (fboundp 'xor)
+      #'xor
+    (lambda (a b)
+      "Exclusive or."
+      (if a (not b) b))))
+
 (defun org-version-check (version feature level)
   (let* ((v1 (mapcar 'string-to-number (split-string version "[.]")))
          (v2 (mapcar 'string-to-number (split-string emacs-version "[.]")))
diff --git a/lisp/org/org.el b/lisp/org/org.el
index 5aa49b29d6..79725ac752 100644
--- a/lisp/org/org.el
+++ b/lisp/org/org.el
@@ -10068,10 +10068,6 @@ org-link-unescape-single-byte-sequence
 	       (char-to-string (string-to-number byte 16)))
 	     (cdr (split-string hex "%")) ""))
 
-(defun org-xor (a b)
-  "Exclusive or."
-  (if a (not b) b))
-
 (defun org-fixup-message-id-for-http (s)
   "Replace special characters in a message id, so it can be used in an http query."
   (when (string-match "%" s)
diff --git a/lisp/play/5x5.el b/lisp/play/5x5.el
index 28748cc351..c5d4659123 100644
--- a/lisp/play/5x5.el
+++ b/lisp/play/5x5.el
@@ -435,8 +435,8 @@ 5x5-make-xor-with-mutation
     (dotimes (y 5x5-grid-size)
       (dotimes (x 5x5-grid-size)
         (5x5-set-cell xored y x
-                      (5x5-xor (5x5-cell current y x)
-                               (5x5-cell best    y x)))))
+                      (xor (5x5-cell current y x)
+                           (5x5-cell best    y x)))))
     (5x5-mutate-solution xored)))
 
 (defun 5x5-mutate-solution (solution)
@@ -931,9 +931,7 @@ 5x5-randomize
 
 ;; Support functions
 
-(defun 5x5-xor (x y)
-  "Boolean exclusive-or of X and Y."
-  (and (or x y) (not (and x y))))
+(define-obsolete-function-alias '5x5-xor 'xor "27.1")
 
 (defun 5x5-y-or-n-p (prompt)
   "5x5 wrapper for `y-or-n-p' which respects the `5x5-hassle-me' setting."
diff --git a/lisp/proced.el b/lisp/proced.el
index 5f35fa34a0..fe687a4f83 100644
--- a/lisp/proced.el
+++ b/lisp/proced.el
@@ -1194,10 +1194,7 @@ proced-time-lessp
 
 ;;; Sorting
 
-(defsubst proced-xor (b1 b2)
-  "Return the logical exclusive or of args B1 and B2."
-  (and (or b1 b2)
-       (not (and b1 b2))))
+(define-obsolete-function-alias 'proced-xor 'xor "27.1")
 
 (defun proced-sort-p (p1 p2)
   "Predicate for sorting processes P1 and P2."
@@ -1208,8 +1205,8 @@ proced-sort-p
              (k2 (cdr (assq (car sorter) (cdr p2)))))
         ;; if the attributes are undefined, we should really abort sorting
         (if (and k1 k2)
-            (proced-xor (funcall (nth 1 sorter) k1 k2)
-                        (nth 2 sorter))))
+            (xor (funcall (nth 1 sorter) k1 k2)
+                 (nth 2 sorter))))
     (let ((sort-list proced-sort-internal) sorter predicate k1 k2)
       (catch 'done
         (while (setq sorter (pop sort-list))
@@ -1219,7 +1216,7 @@ proced-sort-p
                 (if (and k1 k2)
                     (funcall (nth 1 sorter) k1 k2)))
           (if (not (eq predicate 'equal))
-              (throw 'done (proced-xor predicate (nth 2 sorter)))))
+              (throw 'done (xor predicate (nth 2 sorter)))))
         (eq t predicate)))))
 
 (defun proced-sort (process-alist sorter descend)
diff --git a/lisp/progmodes/idlw-shell.el b/lisp/progmodes/idlw-shell.el
index 188ec012cf..e4f46bf882 100644
--- a/lisp/progmodes/idlw-shell.el
+++ b/lisp/progmodes/idlw-shell.el
@@ -2604,7 +2604,7 @@ idlwave-shell-enable-all-bp
   (let  ((bpl (or bpl idlwave-shell-bp-alist)) disabled modified)
     (while bpl
       (setq disabled (idlwave-shell-bp-get (car bpl) 'disabled))
-      (when (idlwave-xor (not disabled) (eq enable 'enable))
+      (when (xor (not disabled) (eq enable 'enable))
 	(idlwave-shell-toggle-enable-current-bp
 	 (car bpl) (if (eq enable 'enable) 'enable 'disable) no-update)
 	(push (car bpl) modified))
diff --git a/lisp/progmodes/idlwave.el b/lisp/progmodes/idlwave.el
index 614d73e23b..1b4b55c94f 100644
--- a/lisp/progmodes/idlwave.el
+++ b/lisp/progmodes/idlwave.el
@@ -8813,9 +8813,8 @@ idlwave-study-twins
 
 ;; FIXME: Dynamically scoped vars need to use the `idlwave-' prefix.
 ;; (defvar type)
-(defmacro idlwave-xor (a b)
-  `(and (or ,a ,b)
-	(not (and ,a ,b))))
+
+(define-obsolete-function-alias 'idlwave-xor 'xor "27.1")
 
 (defun idlwave-routine-entry-compare (a b)
   "Compare two routine info entries for sorting.
@@ -8919,17 +8918,17 @@ idlwave-routine-twin-compare
     ;; Now: follow JD's ideas about sorting.  Looks really simple now,
     ;; doesn't it?  The difficult stuff is hidden above...
     (cond
-     ((idlwave-xor asysp  bsysp)       asysp)	; System entries first
-     ((idlwave-xor aunresp bunresp)    bunresp) ; Unresolved last
+     ((xor asysp   bsysp)     asysp)        ; System entries first
+     ((xor aunresp bunresp)   bunresp)      ; Unresolved last
      ((and idlwave-sort-prefer-buffer-info
-	   (idlwave-xor abufp bbufp))  abufp)	; Buffers before non-buffers
-     ((idlwave-xor acompp bcompp)      acompp)	; Compiled entries
-     ((idlwave-xor apathp bpathp)      apathp)	; Library before non-library
-     ((idlwave-xor anamep bnamep)      anamep)	; Correct file names first
-     ((and idlwave-twin-class anamep bnamep     ; both file names match ->
-	   (idlwave-xor adefp bdefp))  bdefp)	; __define after __method
-     ((> anpath bnpath)                t)	; Who is first on path?
-     (t                                nil))))	; Default
+           (xor abufp bbufp)) abufp)        ; Buffers before non-buffers
+     ((xor acompp bcompp)     acompp)       ; Compiled entries
+     ((xor apathp bpathp)     apathp)       ; Library before non-library
+     ((xor anamep bnamep)     anamep)       ; Correct file names first
+     ((and idlwave-twin-class anamep bnamep ; both file names match ->
+           (xor adefp bdefp)) bdefp)        ; __define after __method
+     ((> anpath bnpath)       t)            ; Who is first on path?
+     (t                       nil))))       ; Default
 
 (defun idlwave-routine-source-file (source)
   (if (nth 2 source)
diff --git a/lisp/simple.el b/lisp/simple.el
index 08021ce0e0..f764c176c9 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -5855,8 +5855,7 @@ exchange-point-and-mark
     (goto-char omark)
     (cond (temp-highlight
 	   (setq-local transient-mark-mode (cons 'only transient-mark-mode)))
-	  ((or (and arg (region-active-p)) ; (xor arg (not (region-active-p)))
-	       (not (or arg (region-active-p))))
+	  (xor arg (not (region-active-p)))
 	   (deactivate-mark))
 	  (t (activate-mark)))
     nil))
diff --git a/lisp/strokes.el b/lisp/strokes.el
index 0c671c43ac..6edf58c7b6 100644
--- a/lisp/strokes.el
+++ b/lisp/strokes.el
@@ -1524,12 +1524,6 @@ strokes-xpm-char-bit-p
   (or (eq char ?\s)
       (eq char ?*)))
 
-;;(defsubst strokes-xor (a b)  ### Should I make this an inline function? ###
-;;  "T if one and only one of A and B is non-nil; otherwise, returns nil.
-;;NOTE: Don't use this as a numeric xor since it treats all non-nil
-;;      values as t including `0' (zero)."
-;;  (eq (null a) (not (null b))))
-
 (defsubst strokes-xpm-encode-length-as-string (length)
   "Given some LENGTH in [0,62) do a fast lookup of its encoding."
   (aref strokes-base64-chars length))
diff --git a/lisp/subr.el b/lisp/subr.el
index eea4e045dd..75bf862324 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -209,6 +209,14 @@ unless
   (declare (indent 1) (debug t))
   (cons 'if (cons cond (cons nil body))))
 
+(defsubst xor (cond1 cond2)
+  "Return the boolean exclusive-or of COND1 and COND2.
+If only one of the arguments is non-nil, return it; otherwise
+return nil."
+  (declare (pure t) (side-effect-free error-free))
+  (cond ((not cond1) cond2)
+        ((not cond2) cond1)))
+
 (defmacro dolist (spec &rest body)
   "Loop over a list.
 Evaluate BODY with VAR bound to each car from LIST, in turn.
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0d5dc0e1c0..a96c1bfd23 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -1770,7 +1770,7 @@ diff-find-approx-text
 	(if (> (- (car forw) orig) (- orig (car back))) back forw)
       (or back forw))))
 
-(defsubst diff-xor (a b) (if a (if (not b) a) b))
+(define-obsolete-function-alias 'diff-xor 'xor "27.1")
 
 (defun diff-find-source-location (&optional other-file reverse noprompt)
   "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED).
@@ -1783,7 +1783,7 @@ diff-find-source-location
 SWITCHED is non-nil if the patch is already applied.
 NOPROMPT, if non-nil, means not to prompt the user."
   (save-excursion
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
 	   (char-offset (- (point) (diff-beginning-of-hunk t)))
            ;; Check that the hunk is well-formed.  Otherwise diff-mode and
            ;; the user may disagree on what constitutes the hunk
@@ -1909,7 +1909,7 @@ diff-apply-hunk
 	(insert (car new)))
       ;; Display BUF in a window
       (set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
-      (diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+      (diff-hunk-status-msg line-offset (xor switched reverse) nil)
       (when diff-advance-after-apply-hunk
 	(diff-hunk-next))))))
 
@@ -1921,7 +1921,7 @@ diff-test-hunk
   (pcase-let ((`(,buf ,line-offset ,pos ,src ,_dst ,switched)
                (diff-find-source-location nil reverse)))
     (set-window-point (display-buffer buf) (+ (car pos) (cdr src)))
-    (diff-hunk-status-msg line-offset (diff-xor reverse switched) t)))
+    (diff-hunk-status-msg line-offset (xor reverse switched) t)))
 
 
 (defun diff-kill-applied-hunks ()
@@ -1958,7 +1958,7 @@ diff-goto-source
       (pop-to-buffer buf)
       (goto-char (+ (car pos) (cdr src)))
       (when buffer (next-error-found buffer (current-buffer)))
-      (diff-hunk-status-msg line-offset (diff-xor reverse switched) t))))
+      (diff-hunk-status-msg line-offset (xor reverse switched) t))))
 
 
 (defun diff-current-defun ()
@@ -2253,7 +2253,7 @@ diff-delete-trailing-whitespace
   (interactive "P")
   (save-excursion
     (goto-char (point-min))
-    (let* ((other (diff-xor other-file diff-jump-to-old-file))
+    (let* ((other (xor other-file diff-jump-to-old-file))
   	   (modified-buffers nil)
   	   (style (save-excursion
   	   	    (when (re-search-forward diff-hunk-header-re nil t)
diff --git a/lisp/windmove.el b/lisp/windmove.el
index ab47565dfa..f5f51480db 100644
--- a/lisp/windmove.el
+++ b/lisp/windmove.el
@@ -592,7 +592,7 @@ windmove-display-in-direction
 the prefix argument is reversed.
 When `switch-to-buffer-obey-display-actions' is non-nil,
 `switch-to-buffer' commands are also supported."
-  (let* ((no-select (not (eq (consp arg) windmove-display-no-select))) ; xor
+  (let* ((no-select (xor (consp arg) windmove-display-no-select))
          (old-window (or (minibuffer-selected-window) (selected-window)))
          (new-window)
          (minibuffer-depth (minibuffer-depth))
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index 0023680738..b3c04cdc9a 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -125,6 +125,13 @@ 'subr-tests--parent-mode
   (should (equal (macroexpand-all '(when a b c d))
                  '(if a (progn b c d)))))
 
+(ert-deftest subr-test-xor ()
+  "Test `xor'."
+  (should-not (xor nil nil))
+  (should (eq (xor nil 'true) 'true))
+  (should (eq (xor 'true nil) 'true))
+  (should-not (xor t t)))
+
 (ert-deftest subr-test-version-parsing ()
   (should (equal (version-to-list ".5") '(0 5)))
   (should (equal (version-to-list "0.9 alpha1") '(0 9 -3 1)))
-- 
2.20.1 (Apple Git-117)


^ permalink raw reply related	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-08-02 10:29                                       ` Mattias Engdegård
@ 2019-08-02 10:59                                         ` Basil L. Contovounesios
  2019-08-06 11:58                                           ` Mattias Engdegård
  0 siblings, 1 reply; 51+ messages in thread
From: Basil L. Contovounesios @ 2019-08-02 10:59 UTC (permalink / raw)
  To: Mattias Engdegård; +Cc: Stefan Monnier, Barry Fishman, emacs-devel

Mattias Engdegård <mattiase@acm.org> writes:

> 1 aug. 2019 kl. 01.39 skrev Basil L. Contovounesios <contovob@tcd.ie>:
>> 
>> Thanks, LGTM except for some minor nits:
>
> Thank you! I've fixed them all according to your suggestions, clarified a few
> more things here and there (mentioning the slight change of semantics of `xor'
> in NEWS), and attached the revised patch.
>
> However, given that the naming and desirability of `equiv' are (amicably)
> disputed, I thought it prudent to prepare a reduced patch with `xor' only, as an
> incremental step if nothing else.

Either looks and sounds good to me.

[Not sure if this is customary, but now that the month has changed,
 perhaps the log entry could additionally link to the 2019-08 part of
 this emacs-devel thread.]

>>> +(when (equiv enabled disable)
>>> +  ;; Toggle state
>>> +  @dots{})
>> 
>> [I would love to see a better and less forced example than this, BTW.]
>
> Replaced with another example -- perhaps no better; you be the judge.

I prefer your example, it's simpler and needs less explaining.

Thanks!

-- 
Basil



^ permalink raw reply	[flat|nested] 51+ messages in thread

* Re: 7 logical-xor implementations in source tree
  2019-08-02 10:59                                         ` Basil L. Contovounesios
@ 2019-08-06 11:58                                           ` Mattias Engdegård
  0 siblings, 0 replies; 51+ messages in thread
From: Mattias Engdegård @ 2019-08-06 11:58 UTC (permalink / raw)
  To: Basil L. Contovounesios; +Cc: Stefan Monnier, Barry Fishman, emacs-devel

2 aug. 2019 kl. 12.59 skrev Basil L. Contovounesios <contovob@tcd.ie>:
> 
>> However, given that the naming and desirability of `equiv' are (amicably)
>> disputed, I thought it prudent to prepare a reduced patch with `xor' only, as an
>> incremental step if nothing else.
> 
> Either looks and sounds good to me.

Thanks, I pushed the xor-only patch since it should be uncontroversial.
I'd still like to add `equiv'; it's just a matter of naming.




^ permalink raw reply	[flat|nested] 51+ messages in thread

end of thread, other threads:[~2019-08-06 11:58 UTC | newest]

Thread overview: 51+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-07-22 18:48 7 logical-xor implementations in source tree Oleh Krehel
2019-07-22 21:47 ` Basil L. Contovounesios
2019-07-24 22:17   ` Basil L. Contovounesios
2019-07-24 23:15     ` Stefan Monnier
2019-07-24 23:44       ` Basil L. Contovounesios
2019-07-25 12:07         ` Mattias Engdegård
2019-07-25 17:28           ` Paul Eggert
2019-07-25 17:46             ` Mattias Engdegård
2019-07-28  7:09           ` Philippe Schnoebelen
2019-07-28  8:04             ` Alan Mackenzie
2019-07-28 19:43               ` Marcin Borkowski
2019-07-30  9:36                 ` Alan Mackenzie
2019-07-30 10:57                   ` Philippe Schnoebelen
2019-07-30 11:28                     ` Andy Moreton
2019-07-30 12:34                       ` Stefan Monnier
2019-07-30 14:25                         ` Barry Fishman
2019-07-31  3:16                           ` Richard Stallman
2019-07-31 15:20                             ` Barry Fishman
2019-07-31 15:42                               ` Stefan Monnier
2019-07-31 20:22                                 ` Basil L. Contovounesios
2019-07-31 21:15                                   ` Michael Heerdegen
2019-07-31 22:28                                   ` Mattias Engdegård
2019-07-31 23:39                                     ` Basil L. Contovounesios
2019-08-02 10:29                                       ` Mattias Engdegård
2019-08-02 10:59                                         ` Basil L. Contovounesios
2019-08-06 11:58                                           ` Mattias Engdegård
2019-07-31 20:31                               ` Michael Heerdegen
2019-07-31 21:38                                 ` Drew Adams
2019-07-31 22:03                                   ` Drew Adams
2019-07-31 22:05                                   ` Basil L. Contovounesios
2019-07-31 23:20                                     ` Drew Adams
2019-08-01  0:09                                       ` Basil L. Contovounesios
2019-07-31 17:12                   ` Marcin Borkowski
2019-07-23  8:39 ` Andy Moreton
2019-07-23  9:08   ` Basil L. Contovounesios
2019-07-23  9:14   ` Mattias Engdegård
2019-07-23 10:26     ` Andy Moreton
2019-07-23 10:40       ` Andreas Schwab
2019-07-23 12:11         ` Andy Moreton
2019-07-23 13:20           ` Basil L. Contovounesios
2019-07-23 13:54             ` Andy Moreton
2019-07-24 22:21               ` Basil L. Contovounesios
2019-07-23 10:44     ` Basil L. Contovounesios
2019-07-23 11:01       ` Yuri Khan
2019-07-25 21:46       ` Juri Linkov
2019-07-23 11:24     ` Noam Postavsky
2019-07-23 12:38     ` Stefan Monnier
2019-07-23 16:41       ` Mattias Engdegård
2019-07-23 17:12         ` Eli Zaretskii
2019-07-23 17:48         ` Paul Eggert
2019-07-23 19:45           ` Stefan Monnier

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