GNU bug report logs - #70155
29.3; Several Emacs Lisp list functions accept non-list arguments

Previous Next

Package: emacs;

Reported by: <tpeplt <at> gmail.com>

Date: Tue, 2 Apr 2024 23:16:02 UTC

Severity: normal

Found in version 29.3

To reply to this bug, email your comments to 70155 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#70155; Package emacs. (Tue, 02 Apr 2024 23:16:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to <tpeplt <at> gmail.com>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Tue, 02 Apr 2024 23:16:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: <tpeplt <at> gmail.com>
To: bug-gnu-emacs <at> gnu.org
Subject: 29.3; Several Emacs Lisp list functions accept non-list arguments
Date: Tue, 02 Apr 2024 19:15:28 -0400
The built-in Emacs Lisp functions ‘last’, ‘nthcdr’, ‘take’,
and ‘ntake’ are functions that accept lists as an argument.
However, they also accept non-list arguments without
signaling an error.  This is not documented in their
docstrings or in the Emacs Lisp reference manual.  The
behavior of the related list functions ‘butlast’ and
‘nbutlast’ is that an error is signaled when the function’s
list argument is not a list.

If it is intended that the functions ‘last’, ‘nthcdr’,
‘take’, and ‘ntake’ should accept non-list arguments without
signaling an error, then this should be documented.
Otherwise, these functions should be changed to behave
consistent with other list functions by signaling an error
when an expected list argument is not a list.

This behavior can be seen by following these steps:

1. Start Emacs at a shell prompt with option ‘-Q’: $ emacs -Q

2. Evaluate the following expressions in the *scratch*
   buffer.  Note that ‘last’ does not signal an error when a non-list
   argument is provided:

(last '(a b c))
;;=> (c)

(last 'a)
;;=> a

(last 3.14)
;;=> 3.14

(last "a string")
;;=> "a string"

3. Evaluate following expressions with the related function ‘butlast’.
   Note that the function signals an error when provided a non-list
   argument.

(butlast '(a b c))
;;=> (a b)

(butlast 'a)
;;=> *** Eval error ***  Wrong type argument: sequencep, a

(butlast 3.14)
;;=> *** Eval error ***  Wrong type argument: sequencep, 3.14

(butlast "a string")
;;=> *** Eval error ***  Wrong type argument: listp, "a string"

4. Evaluate the following expressions for ‘nthcdr’, ‘take’, and ‘ntake’.

As expected, an error is signaled when the (first) number argument is
non-zero and the list argument is a non-list.

But no error is signaled when the (first) number argument is zero and
the list argument is a non-list.

(nthcdr 0 '(a b c))
;;=> (a b c) (correct, as documented)

(nthcdr 1 'a)
;;=> *** Eval error ***  Wrong type argument: listp, a

(nthcdr 0 'a)
;;=> a (expect an error, but got the argument returned instead)


(take 0 '(a b c))
;;=> nil (correct, as documented)

(take 1 'a) => nil
;;=> *** Eval error ***  Wrong type argument: listp, a

(take 0 'a)
;;=> nil (expect an error, but got the argument returned instead)


(ntake 0 '(a b c))
;;=> nil (correct, as documented)

(ntake 1 'a) => nil
;;=> *** Eval error ***  Wrong type argument: listp, a

(ntake 0 'a)
;;=> nil (expect an error, but got the argument returned instead)

--




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#70155; Package emacs. (Wed, 03 Apr 2024 02:56:02 GMT) Full text and rfc822 format available.

Message #8 received at 70155 <at> debbugs.gnu.org (full text, mbox):

From: Drew Adams <drew.adams <at> oracle.com>
To: "tpeplt <at> gmail.com" <tpeplt <at> gmail.com>, "70155 <at> debbugs.gnu.org"
 <70155 <at> debbugs.gnu.org>
Subject: RE: [External] : bug#70155: 29.3; Several Emacs Lisp list functions
 accept non-list arguments
Date: Wed, 3 Apr 2024 02:55:26 +0000
Thanks for filing this bug report.

> The built-in Emacs Lisp functions ‘last’, ‘nthcdr’, ‘take’,
> and ‘ntake’ are functions that accept lists as an argument.
> However, they also accept non-list arguments without
> signaling an error.

As I mentioned in gnu-emacs-help <at> gnu.org, this is the case now, but it wasn't the case until Emacs 24.5.  That's when this regression, or design change, occurred (which was it?).

For function `last' this occurred by simply changing `length' to `safe-length'.  If the intention was only to avoid problems with things such as cyclic lists, then this should be fixed by first ensuring that the argument is a cons or nil.

But if the intention was to also have the effect of handling nonlists such as 3.14 then at least this (odd, non-legacy) behavior should be documented.

`last' is one of the oldest Lisp functions...

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#70155; Package emacs. (Sat, 27 Apr 2024 08:23:01 GMT) Full text and rfc822 format available.

Message #11 received at 70155 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: tpeplt <at> gmail.com, Mattias Engdegård <mattiase <at> acm.org>,
 Stefan Monnier <monnier <at> iro.umontreal.ca>
Cc: 70155 <at> debbugs.gnu.org
Subject: Re: bug#70155: 29.3;
 Several Emacs Lisp list functions accept non-list arguments
Date: Sat, 27 Apr 2024 11:22:06 +0300
> From: <tpeplt <at> gmail.com>
> Date: Tue, 02 Apr 2024 19:15:28 -0400
> 
> 
> The built-in Emacs Lisp functions ‘last’, ‘nthcdr’, ‘take’,
> and ‘ntake’ are functions that accept lists as an argument.
> However, they also accept non-list arguments without
> signaling an error.  This is not documented in their
> docstrings or in the Emacs Lisp reference manual.  The
> behavior of the related list functions ‘butlast’ and
> ‘nbutlast’ is that an error is signaled when the function’s
> list argument is not a list.
> 
> If it is intended that the functions ‘last’, ‘nthcdr’,
> ‘take’, and ‘ntake’ should accept non-list arguments without
> signaling an error, then this should be documented.
> Otherwise, these functions should be changed to behave
> consistent with other list functions by signaling an error
> when an expected list argument is not a list.
> 
> This behavior can be seen by following these steps:
> 
> 1. Start Emacs at a shell prompt with option ‘-Q’: $ emacs -Q
> 
> 2. Evaluate the following expressions in the *scratch*
>    buffer.  Note that ‘last’ does not signal an error when a non-list
>    argument is provided:
> 
> (last '(a b c))
> ;;=> (c)
> 
> (last 'a)
> ;;=> a
> 
> (last 3.14)
> ;;=> 3.14
> 
> (last "a string")
> ;;=> "a string"
> 
> 3. Evaluate following expressions with the related function ‘butlast’.
>    Note that the function signals an error when provided a non-list
>    argument.
> 
> (butlast '(a b c))
> ;;=> (a b)
> 
> (butlast 'a)
> ;;=> *** Eval error ***  Wrong type argument: sequencep, a
> 
> (butlast 3.14)
> ;;=> *** Eval error ***  Wrong type argument: sequencep, 3.14
> 
> (butlast "a string")
> ;;=> *** Eval error ***  Wrong type argument: listp, "a string"
> 
> 4. Evaluate the following expressions for ‘nthcdr’, ‘take’, and ‘ntake’.
> 
> As expected, an error is signaled when the (first) number argument is
> non-zero and the list argument is a non-list.
> 
> But no error is signaled when the (first) number argument is zero and
> the list argument is a non-list.
> 
> (nthcdr 0 '(a b c))
> ;;=> (a b c) (correct, as documented)
> 
> (nthcdr 1 'a)
> ;;=> *** Eval error ***  Wrong type argument: listp, a
> 
> (nthcdr 0 'a)
> ;;=> a (expect an error, but got the argument returned instead)
> 
> 
> (take 0 '(a b c))
> ;;=> nil (correct, as documented)
> 
> (take 1 'a) => nil
> ;;=> *** Eval error ***  Wrong type argument: listp, a
> 
> (take 0 'a)
> ;;=> nil (expect an error, but got the argument returned instead)
> 
> 
> (ntake 0 '(a b c))
> ;;=> nil (correct, as documented)
> 
> (ntake 1 'a) => nil
> ;;=> *** Eval error ***  Wrong type argument: listp, a
> 
> (ntake 0 'a)
> ;;=> nil (expect an error, but got the argument returned instead)
> 
> --

Mattias, Stefan: any comments on this?  Should we document this, or
should we change the code?




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#70155; Package emacs. (Sat, 27 Apr 2024 09:50:08 GMT) Full text and rfc822 format available.

Message #14 received at 70155 <at> debbugs.gnu.org (full text, mbox):

From: Mattias Engdegård <mattias.engdegard <at> gmail.com>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: tpeplt <at> gmail.com, Stefan Monnier <monnier <at> iro.umontreal.ca>,
 70155 <at> debbugs.gnu.org
Subject: Re: bug#70155: 29.3; Several Emacs Lisp list functions accept
 non-list arguments
Date: Sat, 27 Apr 2024 11:48:52 +0200
27 apr. 2024 kl. 10.22 skrev Eli Zaretskii <eliz <at> gnu.org>:

> Mattias, Stefan: any comments on this?  Should we document this, or
> should we change the code?

Eli, thank you for bringing this to our attention. I personally don't see much that we need to change.
Our priorities in Lisp are, arguably:

1. Well-defined behaviour should work as documented.
2. Undefined behaviour (ie, usage errors) should not cause crashes or corruption. This is what we usually call 'safety', or protecting the system's own abstractions.
3. Useful error conditions (such as file-not-found) should work as documented or at least be handled in a predictable and useful way.
4. The implementation should be efficient.
5. Incorrect usage should be detected and signalled as far as pragmatically possible, to help programmers find mistakes in their code.

The low ranking of the last item means that we don't necessarily promise to signal an error for every mistake a programmer can make.

However, in this case there are also algebraic motivations for the behaviour:

> From: <tpeplt <at> gmail.com>
> Date: Tue, 02 Apr 2024 19:15:28 -0400
> 
>> The built-in Emacs Lisp functions ‘last’, ‘nthcdr’, ‘take’,
>> and ‘ntake’ are functions that accept lists as an argument.
>> However, they also accept non-list arguments without
>> signaling an error.

(nth 1 '(a b . c)) does not signal an error nor do we expect it to, so it's reasonable that

  (take 2 '(a b . c)) -> (a b)
  (take 1 '(a . b))   -> (a)
  (take 0 'a)         -> ()

do not signal errors either. nthcdr is the complement of [n]take and behaves the same way with respect to nonpositive arguments, and in fact has the definition

  (nthcdr N L) = (cdr^N L)

for all naturals N, which means that

  (nthcdr 0 'a)       -> a

is correct.

(Of course if you ask me, I'd prefer it if lists were guaranteed to be proper, immutable, with the empty list an object distinct from the symbol nil and the false boolean value. Maybe next year.)

`last` works in the same way:

  (last '(xN ... x1 . x0) M)
  -> (last '(x{min(N,M)} ... x1 . x0) M)

with the base case

  (last '(xN ... x1 . x0) 0) -> x0

ie,

  (last x0 0) -> x0

for any atom x0.
Finally, [n]butlast are just [n]take with a different numeric argument, but here the special case

  (butlast X 0) -> X

is motivated by performance considerations; it's more important to have it go in constant time.





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#70155; Package emacs. (Sat, 27 Apr 2024 14:39:02 GMT) Full text and rfc822 format available.

Message #17 received at 70155 <at> debbugs.gnu.org (full text, mbox):

From: Drew Adams <drew.adams <at> oracle.com>
To: Eli Zaretskii <eliz <at> gnu.org>, "tpeplt <at> gmail.com" <tpeplt <at> gmail.com>,
 Mattias Engdegård <mattiase <at> acm.org>, Stefan Monnier
 <monnier <at> iro.umontreal.ca>
Cc: "70155 <at> debbugs.gnu.org" <70155 <at> debbugs.gnu.org>
Subject: RE: [External] : bug#70155: 29.3; Several Emacs Lisp list functions
 accept non-list arguments
Date: Sat, 27 Apr 2024 14:38:05 +0000
> Mattias, Stefan: any comments on this?  Should we document this, or
> should we change the code?

Please fix the code, to be like, uh, LISP.

Just one opinion.

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#70155; Package emacs. (Sat, 27 Apr 2024 15:02:02 GMT) Full text and rfc822 format available.

Message #20 received at 70155 <at> debbugs.gnu.org (full text, mbox):

From: Drew Adams <drew.adams <at> oracle.com>
To: Mattias Engdegård <mattias.engdegard <at> gmail.com>, Eli
 Zaretskii <eliz <at> gnu.org>
Cc: "tpeplt <at> gmail.com" <tpeplt <at> gmail.com>,
 Stefan Monnier <monnier <at> iro.umontreal.ca>,
 "70155 <at> debbugs.gnu.org" <70155 <at> debbugs.gnu.org>
Subject: RE: [External] : bug#70155: 29.3; Several Emacs Lisp list functions
 accept non-list arguments
Date: Sat, 27 Apr 2024 15:01:11 +0000
I don't speak about the other functions
mentioned.  Fix just `last', and then we
can see what else might need to be looked
at - maybe nothing.  The _regression_ is
the change in `last' behavior.

Elisp's `last' behaved perfectly well, and
the same way as in all other Lisps, until
the regression was introduced, in Emacs 24.

This is a BUG, and it goes against 70 years
of Lisp's `last' function.

> (Of course if you ask me, I'd prefer it if lists were guaranteed to be
> proper, immutable, with the empty list an object distinct from the
> symbol nil and the false boolean value. Maybe next year.)

I see.  Then if you ask me, you don't want
Lisp.  You want something else.

Of course, you can get what you want with
Lisp.  Just don't hijack longstanding Lisp
constructs to do so.  Instead, define new
constructs and don't touch longstanding,
basic Lisp functions and other constructs.

Please restore `last' to its normal, sane,
longstanding behavior.

This bug report was last modified 6 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.