7

In elisp + function without parameters gives 0, (+) ==> 0. * function without parameters give 1: (*) ==> 1. Only (/) gives error: wrong number of arguments.

But in Python entering + without summands give an error: + ==> SytntaxError: incomplete input.

I was wondering what were the justifications for this behavior in elisp.

elisp arithmetic operators

Definition of +

(+ &rest NUMBERS-OR-MARKERS)

Return sum of any number of arguments, which are numbers or markers.

5
  • 2
    emacs.stackexchange.com/tags/elisp/info
    – Drew
    Commented Dec 17, 2023 at 18:53
  • 7
    The closest equivalent of the elisp + function in Python is the Python sum function, not the Python + operator. If you ask Python to add nothing by typing sum([]), it will do exactly the same thing that the elisp + function does: it will return 0. Commented Dec 17, 2023 at 19:36
  • 1
    As @TannerSwett (in the above comment) and @shynur (in a comment below his answer) have said, you should think of + as a sum function, not as a binary operator. The mathematical convention (and it is just a convention, although a very useful one) is that an empty sum has the value 0 and + follows that convention. The reason it is useful is that it allows you to prove things without having to deal with the case of the empty list separately from the case of the non-empty list: it makes proofs shorter and easier. Similarly for * and product.
    – NickD
    Commented Dec 18, 2023 at 2:56
  • 1
    IMHO this is a Lisp question, not just elisp, as @Drew has already noted. If I encountered a Lisp that didn't behave this way, I'd wonder what else was broken. Commented Dec 18, 2023 at 22:05
  • 2
    The brief answer is that + acts as a monoid homomorphism. I don't know how to cleanly express that in Lisp, but in Python, it just means that since integers under addition with identity 0 form one monoid and lists with concatenation and identity [] form another, then sum(a + b) == sum(a) + sum(b) for any two integer lists a and b. Since a == a + [], then sum([]) == 0 so that sum(a) == sum(a) + sum([]).
    – chepner
    Commented Dec 19, 2023 at 16:41

7 Answers 7

10

Math. The sum of nothing is obviously nothing. The product of nothing is more mysterious, because it isn’t nothing. But if you look closely you will soon notice that in both cases you get back the identity element for the operation. The identity element for addition and subtraction is zero, because adding or subtracting zero gives you back whatever you started with. The identity element for multiplication (and division) is one, because multiplying or dividing by one gives you back whatever you started with.

Why do the identity elements matter, you ask? Consider the following sums:

elisp simplifies to
(+ x x x x) = 4x
(+ x x x) = 3x
(+ x x) = 2x
(+ x) = 1x
(+) = 0x

As you can see, the sum of an empty list really is zero. This is a restatement of the proof offered on Wikipedia, which you might take a look at if you want references. A similar proof works for products:

elisp simplifies to
(* x x x x) = x⁴
(* x x x) = x³
(* x x) = x²
(* x) = x¹
(*) = x⁰

Anything raised to the zeroth power is one, so the product of an empty list is also one. It’s the only number that makes sense there. Again there is a proof on Wikipedia, with additional information relevant to set theory and category theory, along with references.


In response to your comment that you still don’t see why + doesn’t complain about missing arguments, look again at the declaration:

(+ &rest NUMBERS-OR-MARKERS)

You can see that it is defined to take no required arguments, and to put all additional arguments into a list called NUMBERS-OR-MARKERS. So you can see right away that it will not give you an error if you don’t pass in any arguments.

15
  • Interesting. I didn't realize (-) was zero in Elisp. (+) and (*) make sense to me, but having the non-commutative one accept no arguments is kind of strange, especially since (- x) is a fundamentally different operation than (- x y). Common Lisp rejects (-), so it's interesting that Elisp chose to diverge from its inspiration on this. Commented Dec 17, 2023 at 19:56
  • Silvio: Elisp was based on MacLisp: maclisp.info/pitmanual/number.html#9.12 (there is also a deviation from that in elisp (or at least in modern elisp), but it's regarding no-argument and single-argument division, rather than subtraction).
    – phils
    Commented Dec 17, 2023 at 21:15
  • 3
    "The sum of nothing is obviously nothing": But 0 is not "nothing".
    – U. Windl
    Commented Dec 17, 2023 at 22:47
  • 1
    @U.Windl I agree that 0 is not nothing, at least for lisp. (+ nothing nothing) gives “unbound variable” error. (+ “nothing” “nothing”)gives type error: “nothing” is not a number.
    – zeynel
    Commented Dec 18, 2023 at 8:22
  • 4
    We have now descended into sophistry...
    – NickD
    Commented Dec 18, 2023 at 17:56
9

This is a very natural outcome.

For instance,

(+ 2 10 30)

you know the result is 42, right?
So, what if we remove the 10?
Obviously, the result will decrease by 10, meaning the result of

(+ 2 30)

is 32.
Then what if we remove the 2 from the above expression?
Exactly, the result of

(+ 30)

will decrease by 2. I.e., the result is 30.
And we finally remove the 30; obviously the result of

(+)

is 30 - 30 = 0.

The same logic can be applied to *.
(Even to the factorial, which is that exclamation mark !. This is why 0! = 1.)

8
  • and why x^0 = 1, etc. Mathematical intuition.
    – shynur
    Commented Dec 17, 2023 at 14:56
  • "what if we remove the 40..." I am not sure. If we remove one of the parameters + function should complain that an argument is missing. (like in Python). So in elisp, it seems, this convention was implicitly added to +. I don't see it in the formal definition of +. (I added that to the question.)
    – zeynel
    Commented Dec 17, 2023 at 16:06
  • 3
    @zeynel The plus sign here actually represents the sum (sigma)
    – shynur
    Commented Dec 17, 2023 at 19:36
  • Actually I think + with less than two arguments should be nil.
    – U. Windl
    Commented Dec 17, 2023 at 22:46
  • 1
    This is a much better answer than the "sum of nothing is obviously nothing" one. Commented Dec 18, 2023 at 11:07
6

This is due to associativity of addition:

Sum(Array1) + Sum(Array2) = Sum(concat(Array1, Array2))

If Array1 is empty, we get

Sum(Empty) + Sum(Array2) = Sum(Array2)

thus Sum(Empty)=0.

See also https://github.com/pandas-dev/pandas/issues/9422#issuecomment-343548481

The python error is due to deficiencies of syntax, not any math. Even in python sum([]) is 0.

3
  • 1
    This also explains why (*) returns 1, and Common Lisp (every #'predicate '() returns T.
    – Barmar
    Commented Dec 17, 2023 at 22:45
  • I agree with the recursive definition, but why isn't it required that + has at least two arguments? Adding an infinite series of "Sum(Empty) +" to a sum doesn't really help.
    – U. Windl
    Commented Dec 17, 2023 at 22:50
  • 2
    @U.Windl: why should we put an arbitrary limitation on #'+?
    – sds
    Commented Dec 18, 2023 at 14:30
4

I think you ask both:

  1. Why are + and * multiple-arity functions and they allow even zero arguments?

  2. Why isn't this the case for / (division)?

Others have answered #1: both + and * have an identity element: 0 for + and 1 for *. 0 + N = N, and 1 * N = N, for any number N.

The answer for #2 is that / (division) has no identity element. There's no number N such that for any number M, N/M = M.

2

I found Empty sum and Empty product.

So + and * do not follow the typical binary operator rules, but the mathematical definitions of sum and product.

Basically it's what db48x's answer said already.

2
  • Good job thinking of looking for proofs on Wikipedia; that didn’t occur to me.
    – db48x
    Commented Dec 18, 2023 at 18:50
  • 2
    There are no proofs, simply because these are conventions: useful conventions and therefore universally adopted in math textbooks - but they are conventions nevertheless.
    – NickD
    Commented Dec 19, 2023 at 6:30
2

Although everyone has been addressing the correctness of the sum of zero values being zero, no one seems to have really taken up the Python aspect of this.

The "syntax" for (e-)lisp + is just like that of any other function call (func args...), i.e. it is a list where the first element is the function to call, and the remainder of the list is the argument(s) to pass. The only oddity is that the name of the function is made up of special characters rather than alphanumeric ones. As such then, the arguments can have any count including zero.

The restriction you see in Python is a syntactic one: The + operator is binary and requires two operands (actually, I'm not sure if unary prefix + exists). This restriction forbids having no arguments, but equally forbids having more than two. You can add several values using several-1 + operations which is just iteratively applying the binary addition operator, but there is no syntax that uses a single + and more than two operands. As some have mentioned, the closest thing is the sum function, which follows the same behaviour (in this aspect, anyway) as the + function in (e-)lisp.

0

As far as elisp is concerned this is what I understood from answers and comments:

(+ 3) and (+) are hardcoded into + function's source code to give (+ 3) ==> 3 and (+) ==> 0 to conform to mathematical conventions. The rest is the subject of mathematics. So, there is nothing else to discuss as far as elisp is concerned.

Not the answer you're looking for? Browse other questions tagged or ask your own question.