
The 2,500 year old history of why Python’s all([]) returns True - earthboundkid
https://blog.carlmjohnson.net/post/2020/python-square-of-opposition/
======
sullyj3

      sum([1,1]) = 1 + 1 = 2
      sum([1]) = 1
      sum([]) = 0
    
      0 + n = n + 0 = n
    

Zero is the unit with respect to addition, so when we add together a list of
no numbers, we ought to get back zero.

you can `from math import prod` in python3.8

    
    
      prod([2,2]) = 2*2 = 2
      prod([2]) = 2
      prod([]) = 1
    
      1 * n = n * 1 = n
    

The same way, one is the unit with respect to multiplication. So, multiplying
no numbers together gets you one.

    
    
      all([True,True]) = True && True = True
      all([True]) = True
      all([]) = True
    
      True && x = x && True = x
    

You get the idea.

~~~
senthil_rajasek
Hmmm...

An identity is something that does not change the left operand

a * i = a

0 for + and 1 for x pass this test.

But for Boolean && neither True nor False pass the identity test because,

( True as identity) False && True = False True && True = True

( False as identity)

False && False False True && False = False

Perhaps there is more to it....

~~~
furyofantares
I think your post indeed shows that

a && True = a

~~~
senthil_rajasek
it makes sense if you look at it as a && True = a. Thanks.

------
housecarpenter
I may be blinkered by my mathematical training, but to me this seems like the
obvious thing to do. Here's an attempt to justify it based on common sense, no
math stuff required. Suppose you do something like

    
    
      if(all(requirements)) { proceed; }
    

If there are no requirements that need to be satisfied, then you should be
able to proceed.

~~~
tprice7
Another way to make it seem more intuitive: "all of these are true" is the
same as "none of these are false" (assuming values must be booleans), and
clearly nothing in an empty list is false.

------
syphilis2
Epicycles. Experience leaves me inclined to believe the code precedes the
definition.

    
    
      def all(iterable):
        for element in iterable:
          if not element:
            return False
        return True
    

[https://github.com/python/cpython/blob/master/Python/bltinmo...](https://github.com/python/cpython/blob/master/Python/bltinmodule.c)

~~~
eru
But that doesn't tell you _why_ that choice is a good idea!

My favourite explanation comes from property based testing.

In general, you have the following property: For any lists xs and ys,

    
    
      all(xs) and all(ys) == all(xs + ys)
    

If you want to keep that property as simple as possible, you have to define

    
    
      all([]) == True
    

If you want to go with the opposite, your property becomes more complicated.

Mathematical definitions are somewhat arbitrary. It's up to us to choose
definitions that make sense in our contexts. A general desire to make the
resulting systems simple and uniform worked out well for many, many context.

Slight detour: that's also why defining 2 - 5 to have an answer is a good
idea! Negative numbers were seen as a bit suspect, but they behave perfectly
sensible. Same for complex numbers.

But: still we generally leave 0 / 0 undefined, because no answer would lead to
a satisfying system.

~~~
majewsky

      all(xs) and all(ys) == all(xs + ys)
    

If I understand you correctly, the "and" should be an "implies" instead:

    
    
      all(xs) => (all(ys) == all(xs + ys))
    

Otherwise this law cannot be true since there exist xs for which !all(xs).

~~~
quietbritishjim
I think they just got their operator precedences mixed up. They meant:

    
    
        (all(xs) and all(ys)) == all(xs + ys)
    

This is why you should always use parentheses unless it's absolutely obvious!
I certainly had to check the docs to find out which way round it goes. I guess
normally you're not dealing with Boolean variables so having == bind tighter
makes more sense:

    
    
        y is not None and x == 7

~~~
eru
Yes, indeed! Sorry, I was deviating from the Python order of precedence.

------
mgraczyk
Another reason we must have `all([]) == True`, because `any([])` should
obviously be `False`.

`all(x) == not any([not v for v in x])`

so `all([]) == not any([])` -> `True`

~~~
eru
That's a strong hint that those definitions would make our lives easier. But
it's not forcing anything?

Also, I don't see why `any([]) == False` would be any more obvious than
`all([]) == True`?

I've seen not-so-carefully designed APIs that violate your 'obvious' logic for
'any':

We had a system that you could send a list of topics, and it would return you
all the datasets associated with those topics. The designer of the system
decided that an empty list of topics would return all datasets, instead of
nothing.

I suspect their reasoning was that giving people an option to get all datasets
was useful, and that predictably returning nothing was useless.

But what they should have done in this situation for a clean API was to take
an optional list of topics.

~~~
erik_seaberg
If you view any and all as reductions

    
    
      any([x, y]) == x or any([y]) == x or y or any([])
      all([x, y]) == x and all([y]) == x and y and all([])
    

then any([]) must be false (otherwise x and y would never matter), and all([])
must be true.

In the query API you're describing, does {topic1, topic2} find datasets on
(topic1 OR topic2), or is it (topic1 AND topic2)? That's what determines
whether more topics means more or fewer results, and whether {} should
correctly find nothing or everything.

~~~
eru
> [...] then any([]) must be false (otherwise x and y would never matter), and
> all([]) must be true.

Yes, I know. My problem is with:

> Another reason we must have `all([]) == True`, because `any([])` should
> obviously be `False`.

Argument is of the form 'A, because B'. But I don't see how B is any more
obvious than A here. They seem about equally obvious.

------
TimonKnigge
`all(x + y) = all(x) and all(y)` even when `x, y` can be the empty list.

------
wyager
The identity element of the conjunction (“and”) monoid is True. Any confusion
here seems like a good reason we should teach students functional programming
- this is straightforward if you think in terms of folds over algebraic
objects.

I can’t actually think of a coherent argument for returning anything _besides_
true.

~~~
eru
I'm in your camp. But the best argument I found is:

"This is a confusing situation, and no boolean answer we could give would
satisfy the principle of least surprise, so we should throw an error."

I don't think there's a good argument to be made for returning False.

As an aside: I don't think there's anything particularly related to
'functional programming' about the arguments used here. But, yet, experience
with functional programming tends to make people more careful about finding
general principles that give you answers for corner cases like this, too.

The imperative code used in the Python standard library is just as compelling
an argument for the Right Answer also being the simplest as any based in FP:

    
    
      def all(iterable):
        for element in iterable:
          if not element:
            return False
        return True
    

In general, when I'm reviewing imperative code, I'm encouraging the authors to
find ways to handle corner cases like empty lists organically within their
code instead of with a special case check at the top. The organic handling
seems to have a higher chance of producing the Right Answer.

~~~
wyager
> no boolean answer we could give would satisfy the principle of least
> surprise

I think True follows the principle of least surprise.

> I don't think there's anything particularly related to 'functional
> programming' about the arguments used here

It imparts a better understanding of algebraic abstractions, including what it
means to recursively reduce a data structure.

I'm not sure how that python code is supposed to be especially simple or
elucidating. I think the following Haskell code is reasonably elucidating,
since it elides any need to manually define a special case or base case:

    
    
        all = getAll . foldMap All
    

Where "All" is the conjunction ("and") monoid, as defined in Data.Monoid.

~~~
eru
> I think True follows the principle of least surprise.

Me too. I was just quoting the best argument (I found so far) for a different
position.

> I'm not sure how that python code is supposed to be especially simple or
> elucidating. I think the following Haskell code is reasonably elucidating,
> since it elides any need to manually define a special case or base case:

The Python code wasn't for comparison to any Haskell implementation. If you
can read Haskell, you'll likely prefer the Haskell version. But that's not the
point.

The point is that amongst all imperative attempts, the most straightforward
ones also naturally suggest what to do about the empty list.

Btw, in Haskell notation most normal people's mental model of how 'all' works
is probably closer to:

    
    
      all = foldl1 (&&)
    

Most normal people don't think about neutral elements of operations, and that
they suggest a natural answer for folds over empty collections.

------
leetcrew
idk why everyone is treating this like it's some complicated mystery. it's
defined to be consistent with universal quantification (ie, ∀x) that most of
us should have learned in intro to discrete. std::all_of works exactly the
same way.

~~~
capableweb
> most of us

Who are "most of us"? People on HN? People who do programming?

I've a professional programmer, but horrible at math. Don't have any formal
education and even less education about discrete math. So for me this was
educational and I thank the author for putting it down in writing and sharing
the knowledge.

I'm sure I'm not alone on HN or in the software industry with this.

I'm also sure I'm sitting on bunch of knowledge you have no idea about, but
when others share that, I don't shoot them down for it, I'm happy we're all
helping each other understand things.

~~~
leetcrew
the article itself is a decent intro explanation. I'm mainly talking about all
the comments in this thread getting into monoids and abstract algebra when a
sufficient explanation can be found in first-year cs material. it's a
consequence of how all_of and any_of relate to each other.

~~~
capableweb
I see. Since you made a top-level comment, it seems you're commenting about
the article itself. If you're replying to comments, I suggest you use the
reply functionality and it all becomes a little bit clearer for everyone.

I, for one, also like the comments here as they expand and dive into more
topics that again, "some of us" have not read/heard about before so grateful
for that.

------
ummonk
I suspect the Greeks are being mischaracterized here.

Would they, for example, have considered the statement "all unicorns have a
horn" to be false?

In fact, the post mentions the statement "all stonemen are made of stone"
which is assumed to be true under the greek logic, but according to the
explanation should be false because there are no stonemen.

~~~
eru
Your last example should be "all stonemen are made of wood". That would be
true under one of the modern interpretations, but false under the Greek one?

Your example ""all stonemen are made of stone" would be true under both modern
and supposed Greek interpretations.

~~~
earthboundkid
I think Boethius would say all stonemen are made of stone is false.

~~~
eru
Hmm, I guess I have to re-read.

------
jhanschoo
I'd like to note that Socrates' syllogisms only address a small fragment of
predicate logic. It coexisted and competed with the propositional logic of the
Stoics. Predicate logic did not achieve its power as we recognize it today
until Frege.
[http://hume.ucdavis.edu/mattey/phi112/history.html](http://hume.ucdavis.edu/mattey/phi112/history.html)

------
wirrbel
Works as expected I'd say.

------
enriquto
Unfortunately, the min and the max of empty python lists are not +INFINITY and
-INFINITY, as they should be for similar reasons.

~~~
oefrha
That wouldn't make any sense.

    
    
      >>> min(['a', 'b'])
      'a'
      >>> min(['a'])
      'a'
      >>> min([])
      float('-inf')  # ???
    

An empty list is not a list of numbers. It's not a list of anything.

(You can use typing hinting to indicate the type of an empty list, but type
hints are ignored by the interpreter.)

~~~
enriquto
I'd rather argue that "min" only makes sense for numbers and not for other
objects.

~~~
oefrha
No, min makes sense for any set with a total order. Sometimes partial order
too.

~~~
throwaway_pdp09
Integers have an (intrinsic & natural) total order. Do characters? I'd say no.
We represent them as integers, then expose those underlying integers because
it's extremely useful for efficiency purposes, but I'd pretty strongly argue
that that's an optimisation only and not an intrinsic to the alphabet.

'a' < 'b' only by accident of history. It could just as easily have been the
reverse.

~~~
eru
To give some more examples:

Tuples have a reasonable natural total order: just compare component wise. And
minimum of a list of tuples makes sense.

You could argue that alphabetic ordering is artificial, but it's hard to argue
that it ain't well established and non-surprising.

Interestingly, not all numbers have intuitive orders. Eg complex numbers don't
really have a good preferred ordering. And the ordering of p-adic numbers is
well defined and unique, but really weird.

~~~
throwaway_pdp09
> Tuples have a reasonable natural total order

No they don't. (1, 2) > (2, 1) = ???

To compare tuples you need a generalisation of comparison which includes
'incomparable' as an outcome. See lattices
[https://en.wikipedia.org/wiki/Lattice_%28order%29](https://en.wikipedia.org/wiki/Lattice_%28order%29)

> but it's hard to argue that [alphabetic ordering] ain't well established

it is, agreed...

> and non-surprising

...agreed again, only because it's a convention you know so well (had it been
reversed since your birth you'd say exactly the same thing).

I agree with both points but that does not invalidate what I said abour
alphabetic ordering being artificial.

~~~
eru
Oh, I think I wasn't careful enough with my language.

I meant that the common ordering on tuples is reasonably natural. Not that it
is necessarily the One True Natural Ordering.

About lattices and partial orders: if there's a choice about what to pick as
the default ordering for some built-in datatypes for your language and there
are multiple reasonable choices, going with the total order seems like a good
idea.

To give a counterexample: functions from natural numbers to an orderable set
have a well defined, reasonably natural order, just compare them like as if
they were a list of elements of the orderable set. But that would be a bad
idea in a programming language, and it is indeed better to just treat
functions as incomparable by default.

~~~
throwaway_pdp09
I think we can certainly agree on this. Thanks.

------
cjfd
In logic non-existing objects have all properties, including contradictory
ones. This follows from the fact that in propositional calculus from a
contradiction (or from False) everything can be concluded. I.e., P /\ ~ P ->
Q.

One could conclude that the definition of -> is not very logical but if one is
to attach a formal meaning to -> it would seem hard to avoid this. One would
need conditions like that when P -> Q we have to have that P and Q are related
in some way but that does not sound like something that is very easy to
formalize.

It also does not matter whether one uses intuitionistic logic. P /\ ~ P -> Q
still holds.

In type theory False is the empty type and proving something about it can be
done with case analysis. Since it is the empty type there are zero cases and
one is immediately done and can prove anything.

------
sn41
This is in fact the logically meaningful thing to do. All must be monotone
decreasing in set inclusion.

Other similar definitions are that min of an empty set is infinity and max of
an empty set is minus infinity. Min is monotone decreasing over set inclusion
hence min of empty set must be the maximum possible value. Max is montone
increasing in ser inclusion hence max of empty set must be the smallest
possible value.

------
rmrfchik
I think the very same is happening in scheme. Scheme's functions naturally
works on lists: +, * , and, or. Applying it on empty lists gives us:

    
    
      > (and)
      #t
      > (or)
      #f
      > (+)
      0
    

And most surprising (although it's very convenient on second thought):

    
    
      > (* )
      1
    

Thus multiplication on empty list gives 1.

~~~
chewbacha
I believe that’s likely acting as a reduction with an initial value. When you
sum you start with 0 and when you multiply you start with 1. These provide
identity functions for the first addition and multiplication.

1 * x = x

0 + x = x

------
globular-toast
The reason it's like this on python has nothing to do with philosophy and
everything to do with algebra. The mathematical terminology sadly escapes me
but essentially for each operation you want there to be a value such that
application is a no-op. For addition this is 0, for multiplication 1, for and
it is True and for or it is False. You want the n-ary versions of the
operations to return those values for the 0 arity case. So sum([]) = 0,
all([]) = True and if there was one product([]) = 1. This makes the
mathematics very uniform and beautiful.

Lispers know this implicitly. You know they say learning Lisp makes you a
better programmer? This is an example of that. Raymond Hettinger and many of
the Python authors are Lispers are heart and this is obvious to them.

~~~
umanwizard
The identity element is the term you’re looking for

~~~
globular-toast
Yeah. Multiplicative identity, additive identity etc. Not sure what the name
of the "andity" identity would be. Maybe just "identity of the and operation".

~~~
umanwizard
Logical "and" is also called "conjunction", so you would call it the
"conjunctive identity". "False" would be the "disjunctive identity".

------
smabie
The answer: otherwise the existential quantifier wouldn't work (since you can
transform forall x into there does not exist a not x). When you use the
existential quantifier, it makes sense. I'm not sure how this is relevant to
python, this question is purely in the domain of first order predicate logic.

------
tny
Is there any language where this isnt true?

~~~
earthboundkid
English.

------
smitty1e
Qualitatively, one is reminded of dead-beat controllers[1].

As a theoretical matter, "all unicorns are blue" is like a transfer function
for a controller that can move an elevator from the bottom of the Empire State
Building to the top in 0.01 seconds. Connecting words is like solving a math
problem.

Trying to locate that unicorn, or bringing that controller out of state space
and into reality, one quickly realizes the non-existence of unicorns and that
the energy applied to that elevator in order to meet the time requirement
would _vaporize_ the equipment.

Requirements and metadata need not meet the realities that apply to data.

[1][https://en.m.wikipedia.org/wiki/Dead-
beat_control](https://en.m.wikipedia.org/wiki/Dead-beat_control)

~~~
eru
That's intriguing, but I'm a bit confused and don't really see the connection.
Could you expand?

~~~
smitty1e
My point was that as long as we're cranking formulas or stringing words
together, much can seem plausible.

Hard reality does not always agree.

Nor do HN moderators, apparently.

~~~
Armisael16
They may have simply not understood. I’ve read your post half a dozen times
and I’m still not sure what you’re trying to say.

~~~
smitty1e
Maybe I just suck like the blue unicorn.

------
amai
Shouldn't all() simply throw an exception in case of an empty list?

------
co_dh
True is the unit of logic and as a monoid. You know math is useful.

------
chrismsimpson
In Swift, I would image I could define this method as throwing an error in
such a case, nixing the debate entirely.

Why Swift specifically? This would be in the calling signature, implying there
are inputs you can give said method that are nonsensical.

~~~
eru
You'd just open another debate.

Your client code has to work with the behaviour of your function.

If you have a perfectly sensible answer for a corner case, just give that
answer. Otherwise, you are forcing unnecessary extra case handling on your
client code.

And that principle also tells you which answer your code should be giving:
whatever makes the client code simpler.

Another example, perhaps more inspired from C conventions:

Imagine you have some functions that take a timeout as an argument. After the
time is up, they should fail, if they haven't produced an answer yet.

Now, in these kinds of situations you often find that people choose 0 to mean
an infinite timeout.

That's a bad idea, because it violates simple invariants like: if you run two
statements with timeouts of t0 and t1, after at most t0 + t1 seconds, you
should either have an answer or get an error.

(You can of course complicate the invariant by adding special cases for t0 or
t1 being zero. But who likes special cases?)

------
maest
This is in no way Python-specific.

------
foxes
Monoid morphism.

------
haecceity
Maybe it should throw an exception

~~~
aivisol
Why? All unicorns are blue, aren't they?

~~~
tzs
I’ve seen a lot of experimental evidence for that. "All unicorns are blue" is
logically equivalent to "all non-blue things are not unicorns".

Over the past few decades I’ve seen ten of thousands of non-blue things, and
none of them were unicorns!

[https://en.wikipedia.org/wiki/Raven_paradox](https://en.wikipedia.org/wiki/Raven_paradox)

~~~
jerf
Contrapositives are one of my favorite things in simple logic to demonstrate
the difference between rigorous logic and our human heuristic logic.
Mathematically, contrapositives are identical to the original statement, but
extremely frequently, if not almost always, they _sound different_ to us
humans.

If more people understood them, there's a number of dumb internet debates that
could finally die, where X -> Y isn't immediately obviously wrong to our human
brains and may even sound correct but !Y -> !X _is_ immediately obviously
wrong.

------
celias
all([]) returns True but any([]) returns False, which seems like a
contradiction.

~~~
mlazos
The way I think about it is the base case of a recursive function to evaluate
the two. In order to preserve folding Boolean AND across a list your base case
must be true. Folding Boolean OR across a list only works if your base case is
false.

~~~
xscott
False is the identity element for Or, similar to how Zero is the identity
element for addition.

True is the identity element for And, similar to how One is the identity
element for multiplication.

