Subject: Re: bug#38708: [PATCH] Deduplicate flonum and bignum constants in bytecode

From: Mattias Engdegård <[email-masked]>
Date: Sat, 28 Dec 2019 19:50:14 +0100

28 dec. 2019 kl. 17.36 skrev Pip Cet <[email-masked]>:

> It was part of the rather extensive eq-vs-eql debate, I'm afraid.

Thank you, I think I have heard most of the possible arguments in one form or the other over the years...

> I'll try to be clear about this: I think anything but making eq and
> eql synonymous is likely to cause problems that drive programmers away
> from Elisp.

Actually, I can think of a bunch of more pressing issues, both in terms of the language, its implementation, and the framework.

> But there are axioms that programmers can rely on that are
> stronger than what eq actually promises. For example, two objects
> might be thought either to be eq to each other or not, but code such
> as this:
> 
> (defun my-not-eq (x y) (not (eq x y)))
> 
> (defun always-t ()
>  (or (eq 18446744073709551616 18446744073709551616)
>      (my-not-eq 18446744073709551616 18446744073709551616)))
> 
> (byte-compile 'always-t)
> (always-t)
> 
> will yield unexpected results.

Elisp has a few 'boundedly undefined' parts which would be avoided in a greenfield design but that we are more or less stuck with for the time being, and probably should paint in large letters on the first page of the manual. Such as: don't use eq for numbers; don't mutate literals (strings, conses, vectors); etc.

>> I'm not sure I understand. Surely such a criterion imposes a rather low limit on permissible optimisations?
> 
> Yes, it does. I think any change in the behavior of eq, except for
> making it equal to eql, is likely to break code that's out there
> somewhere (and that doesn't mean we hear about it; more likely, people
> end up abandoning Elisp and using JavaScript instead). So the second
> best option is to keep the current (pre-patch) behavior in which (eq
> 1.0 1.0) is reliably nil, IMHO.

(Javascript's notion of equality, I'm told, is not quite a model of elegance and simplicity, but perhaps that was your point.)

In my experience people seem to have little trouble understanding that eq shouldn't be used to compare numbers. If anything, the introduction of bignums helps driving the point home: up to and including Emacs 26, elisp programmers could get away with eq for integers but not floating-point numbers. Now, the rules are simpler. (Characters still get a free pass, I suppose.)

While we could document a safe range of fixnums for which eq applies, it's probably better not to.

I definitely don't think it's worth propping up broken code that somehow relies on the non-identity of float literals (bignums isn't a worry yet). Such code is not only wrong, it's fragile: fragile in the face of changes to elisp, and of innocent-looking changes to the code itself, such as introducing variables for values.

This doesn't mean that I would necessary be opposed to making eq a synonym for eql; just that it would be a rather more momentous decision that I won't bore anyone by discussing here (although I'd be happy to talk about it over a drink if we meet one day). Has any state-of-the-art Scheme or Lisp implementation ever taken that step?

> Technically, I think code such as
> 
> (cond ((eq a b) 1) ((not (eq a b)) 2) (t 3))
> 
> is allowed to return 3. We should attempt not to make changes that
> make this more likely, though.

Given the state of elisp byte-code optimisation, there is plenty of room for improvements before such semantics become unavoidable. In particular, the committed change does not in any way enable such paradoxical behaviour.

> (Again, is this really what we want? If you can't modify an eq-based
> hash table reliably, because keys might have become eq (and thus
> overwrite each other) that weren't eq when you checked the hash table,
> what can you use such hash tables for?)

Are you arguing that this would be a consequence of the constant deduplication? When eq-based hash tables are not for use with numeric keys anyway?

> You're right, that is eq's contract. But people do misuse it, and one
> of the things they wouldn't expect is that optimization results in
> things becoming non-eq that were eq before optimization; I think the
> other direction is much less problematic.

Good thing that the change works in that other direction then!

>> What would anyone gain from such a restriction? And the change is minor because it's a small thing to do; what I thought looked like an obvious oversight, or one that made more sense back when Elisp didn't have bignums.
> 
> Well, Elisp has had floats for a while, and bignum constants appear to
> be nonexistent, so far.

In other words, there is no broken bignum code that can break.

Obviously it's a small change, and one that I'm prepared to revert if required. But I would like to understand the reason and principles behind it. I want elisp to be faster; we have promised exactly nobody that each numeric literal has its own identity, nor is that a reasonable assumption by a programmer to make.