Why does all() return True if the iterable is empty?
blog.carlmjohnson.net
blog.carlmjohnson.net
The product of no numbers is 1.
The union of no areas (sets, …) is the empty area.
The intersection of no areas (sets, …) is the universal area.
The commonality here, is what element z can you add or remove from a list which won’t change the result?
We call the element that makes no change to the result, the “identity element”.
For y = sum of {x1,x2, … Xn, z},
We know z must be 0, if it has no impact on y. So the identity element for sum is 0.
So if y = sum of {}, we know we can insert the identity element without changing the result, and we know the sum of {0} is 0. The identity element itself.
So operations applied across lists return that operation’s identity element for an empty list.
—-
Ergo:
the identity element of “all” is “true”.
The identity element of “any” is “false”.
——
More fun:
The identity element for the resistance sum across parallel resistors, is the infinite ohm resister.
The identity element for the vector distance sum of a child making a sequence of jumps is the in-place jump.
—-
Getting meta with, oh hell, monads:
The identity element for C, the concatenation operator over lists, is the empty list {}.
Since all lists are concatenations of lists (down to empty & unitary lists), we can view any operation Q over lists also as an operation over the concatenation of lists.
So any Q acts like a monad over list concatenation operations.
If Q = sum, then it is the monad that transforms concatenations of lists of numbers into the sum of those numbers.
If Q = all, then it is the monad that transforms concatenations of lists of truth values to the conjunction of the truth values in those lists.
With that view in place, we can see another reason why the identity of any operation Q is Q on an empty list Q{}
Because as a monad, Q applies over C, the concatenation of lists. The identity of C is {}, so the identity of Q must be Q{}.
Voila!
So we really shouldn’t define sum{} as 0, but define 0 as sum{}.
“0” is just a symbol. Sum{} is what it is.
0 is the sum of nothing.
So “True” IS the conjunction of nothing. It’s meaning IS all{}. It is the truth of “no requirements”.
The cross points, over multiple points on segments in different dimensions is a product.
And the product across an empty list is 1.
The number of ways you can roll a dice “no” times is also 1.
Cross combinations of all kinds are products.
Does this imply there's a sixth of a way to unroll a dice?
The number of elements in an n-dimensional array is the product of its n dimension sizes. For example, the number of elements in a two-dimensional 2x3 array is 6, because 2 x 3 = 6.
Consequently, the number of elements in a zero-dimensional array would be the empty product, which is 1 (as the root comment mentions).
Naively, one might think that removing a dimension from an array cannot increase the number of elements. For example, dropping a dimension from a 2x3 array reduces the number of elements from 6 to 2 (or 3, depending in which dimension you drop). But in the case of a zero-length 1-dimensional array (i.e. an empty list or empty vector), dropping the one dimension causes an element to be added, because a zero-dimensional array always has 1 element.
(This effect is not restricted to 1- and 0- dimensional arrays, by the way. If you admit zero-length dimensions, then you can also think of a 2x0 two-dimensional array, which would have zero elements, but dropping the zero-length dimension would result in a 1-dimensional array of length 2, increasing the element count by 2.)
The other maybe surprising part is that for all n > 0, you can have an n-dimensional array with zero elements by having at least one of the dimensions have length zero. But for a zero-dimensional array, it’s not possible for it to be empty, because it has no dimension whose length could be zero. This is somewhat counterintuitive at first, because one might have thought that a zero-dimensional array would be the canonical empty array, like how a point in space (whis is a zero-dimensional object) has no length/area/volume.
By the way, one could think of non-array values in a programming language to really be zero-dimensional array values, with their value being the singular element of the zero-dimensional array.
---
a monoid is a set A with an associative binary operator @ on it: that is, for any a, b, c
a @ (b @ c) = (a @ b) @ c
and it also has an "identity" element I, that is, for any a:
a @ I = I @ a = a
common monoids: integers under addition (i.e. @ is addition, the identity is 0), nonzero real numbers under multiplication, lists under concatenation, booleans under AND (identity is True), booleans under OR (what's the identity?), ...
now, when you have a mathematical structure, you often want to study functions between two of them. so you might ask what a reasonable notion of functions between monoids is
one nice way is if, given two monoids, M with operation @ and unit I, and N with operation ^ and unit J, we had a function f for which:
f(I) = J
f(a @ b) = f(a) ^ f(b)
this is loosely described as the function "respecting the structure" of the monoids, and such functions are called monoid homomorphisms or just homomorphisms if the context is clear
--
with that background, we see that `all` should really be be a homomorphism from the monoid of lists (under concatenation, with the empty list as the identity element) to the monoid of booleans under AND (with True as the identity)
this means that
all(concat(list1, list2)) = and(all(list1), all(list2))
or, in python,
all(a + b) = all(a) and all(b)
and, of course, all([]) = True!
the value here is that now you can reason about the all() of two lists by thinking about the all() of sublists, and the conditions required for a homomorphism guarantee that the empty list is not an edge case:
all([T, F]) = all([]) and all([T, F]) = all([T]) and all([F]) = ...
and that it doesn't matter how you cut up a list into sublists and what order you apply the concatenations in to reconstruct it
For instance, a transform that wraps each joining operator, in an expression tree, with a new operator that applies the original operator if all its inputs are defined, but returns a null if any input is null.
The expression tree structure hasn’t changed, but it now consistently (anvoiding error) responds to null values by propagating them.
In my example, a monad converts the list combining operation of concatenation into other list combining operations.
The result was that the identity of concatenation (the empty list) formed the basis for the new list operator’s identity.
—
Both concepts are indeed relevant.
A “monoid-to-aggregator” monad could convert 2-element operations into recursively applied list operations (which also return the single element for unitary lists, and the identity element for empty lists).
Then a structure of monoids processing pairs of values could be transformed into a structure that processes lists.
Note that this is inherently a constructible relation, which avoids many pratfalls of more general mathematics.
But if ANY & ALL are being defined as general mathematical relations over all EXISTING members of a class (whose members’ construction, consistency or completeness, may be unknown or even unattainable), then there are other issues.
TRUE is the identity element of "and", not "all". "All" is not a binary operator, it is the (unary) aggregation of "and". That is, S.all() == S.aggregate((prev, next) => prev && next, TRUE).
FALSE is the identity element of "or". "any"/"some" is the aggregation of "or". S.any() == S.aggregate((prev, next) => prev || next, FALSE).
0 is the identity element of addition, and "sum" is the aggregation of addition. S.sum() == S.aggregate((prev, next) => prev + next, 0).
1 is the identity element of multiplication, with "product" the aggregation.
Given a monoid M<T> { combine(a: T, b: T) => T, identity: T }, you can define an aggregation operation the same way:
S.aggregate((prev, next) => M.combine(prev, next), M.identity)Being an identity is a property. A given mathematical object can be an identity for more than one relation.
It can also be the identity element for general logical propositions or relations.
The reason being, the former is just a special case of the Boolean relations of AND and ALL.
So TRUE can consistently be the identity for many classes of logical operations of varying generality or complexity.
The raw identity relation and concept of identity element form the “tops” of ladders, whose specializations can hold over large classes of relations.
add | sum
multiply | product
and | all
or | any
concat | "flatten" (arguable name here)
I also find beauty in the fact that the existence of a monoid on the left is sufficient for producing the aggregation on the right that behaves in the way we want. I see the monoid as the simple building block from which we produce more interesting and complicated structures. I enjoy the continued, unbroken thread of rigorous logic that connects everything.When you say that TRUE can be the identity of "an operator that extends 2-value Boolean operations to lists of N-elements", I just don't see the unbroken thread of rigorous logic behind that statement. Maybe it's there! But I don't see it. I can write down exactly what it means for some element i to be an identity of a binary operation @. It means i@x == x == x@i for all x in the set over which @ is defined. Whatever the rules of "identity" are that you're talking about, they're not this, so I'm just kinda struggling to accept it. I wonder whether maybe what you're thinking of deserves a different name than "identity".
Does any of this matter? It does to me, because I actually use Monoids all the time in programming, where you can't afford any ambiguity at all. So again: maybe you see that unbroken chain of rigorous logic and know exactly what you mean when you say TRUE is the identity of "an operator that extends 2-value Boolean operations to lists of N-elements", but I'd need to see the exact laws that an element must follow to be such an operator or such an identity of that operator. I gave an example of how to turn associative binary operations with identity into a unary operator on lists of elements, but unary operators don't really have identity elements. Some unary operators have fixed points and eigenvalues, which are similar to identity, but such operators must have the same domain and codomain, and aggregate operators like SUM and ALL do not. So there's not really anything you define to be SUM's identity, since it does not accept 0 as an input.
TRUE for a monoid can exist independently from any aggregator defined over that monoid.
My bad :)
The fractal lines between what is mathematically true, vs. what is practical or useful to be true, in coding are worth paying attention to!
Thank you!
And perhaps these relations can always hold, “without loss of generality”.
In which case we need to formally define these monoid-aggregator relations so we all know when they are assumed to be in effect.
—
On the beauty side, I love how 0 = sum{} captures the semantic difference between zero and other numbers, with the syntax of nothing between two braces.
If we define an "aggregation" agg over some set S as a function from S* (the "Kleene star", AKA the free monoid over S, AKA really just simple arrays of S) to S, and we identify some element i such that
agg([i] concat X) == agg(X) == agg(X concat [i])
for all X in S*, that does feel very "identity-ish". It's a little bit like saying [i] is an identity of concat under the equivalence relation on S* that X~Y iff agg(X) == agg(Y)? In the same way that 7 is an identity of + under the equivalence relation "mod 7" (paraphrasing).But note that:
sum([-2, 2] concat X) == sum(X) == sum(X concat [-2, 2])
for all X as well. You could say "well obviously, because sum([-2, 2]) == sum([0]) and [0] is our identity element" but then we're implicitly assuming that agg(X concat Y) = agg(X) @ agg(Y) for some operator @. Which in turn is basically assuming we've built agg up from a monoid to begin with, and the novelty here would be what happens when we don't do that. There may be some interesting (if not useful) stuff here.if there exists some operator @: (S, S) -> S such that:
agg(X concat Y) == agg(X) @ agg(Y)
For all X, Y in S*, and if : agg([i] concat X) == agg(X) == agg(X concat [i])
for all X in S*, then must agg([i]) be an identity element of @? Certainly that's true if agg is onto (i.e. we can find every element of S via agg of some S*). But it can't really be true in general, because we could make degenerate agg functions like ignore(X) := 0
Now every element of S meets this condition: ignore([s] concat X) == ignore(X) == ignore(X concat [s])
So we have every element of S as this weird kind of "identity" of the "ignore" function. And we can find functions on S* with no "identity" of this kind: "count", for instance. Although that's cheating because it only works when S happens to be the natural numbers (otherwise agg is not a function from S* -> S).My logic depended on the aggregator being a recursive monoid, with:
1) Special handling for a unit list, where the unitary element is returned, and
2) Special handling for an empty list, where the identity element of the monoid is returned OR we get explicit that the case of the empty list is simply already it’s simplest form, and we choose to define that equivalent to the monoid’s identity
So i skipped a step by “finding” the identity from the aggregator’s behavior, instead of directly from the monoid it is based on.
——
Alternatively, I am liking the idea of defining this list vs pair, aggregator vs monoid relations more tightly.
Empty braces unify the syntax for “zero” with the semantics “nothing” between the braces.
How else can you join those semantics and syntax?
Likewise, starting with unary natural numbers, syntactically and semantically, a list of 1’s is the number.
So which comes first? A unary monoid addition of 2 lists of ones (n-way concatenations of unitary 1), or an n-way sum-concatenation that each unary number actually consists of?
I think they are more tightly bound definitions than our typical syntax suggests, when we get down to the semantics = syntax level.
f(i, a, b) == f(a, i, b) == f(a, b, i)?
For all a, b in f's domain? The seems like the obvious choice, and certainly holds for ternary functions "built up" from associative binary ones, but I've just never seen such a thing. Is there a case where it's useful to talk about the identity of a 3-valued function?I think the thing I find weird about this is that a binary operator op2 over some set S is defined as S^2 -> S. So we can sensibly say:
op2(a, i) == a == op2(i, a)
But the extension of this to a 3-valued function from S^3 -> S cannot have that middle condition, because it has too many "free variables". We can't say: op3(a, b, i) == op2(a, b)
Without establishing a relationship between op3 and op2, but must there be a sensible "decomposition" of op3 into some op2? Surely not. I guess I just don't know what it means to be an identity of a 3-valued function.As an example, consider mult3(a, b, c) := a * b * c. Then,
mult3(0, a, b) == mult3(a, 0, b) == mult3(a, b, 0)
for all a and b. We intuitively know that 0 is not behaving like an identity. Can we write down an identity rule that must hold for a generic op3 that excludes 0 as the identity of that function? Without relying on some oracle to provide us an appropriate decomposed op2?Edit: I would also like to point out this function over R^3:
f(a, b, c) := a*b + a*c + b*c
Note that every value in R meets the condition: f(a, x, y) == f(x, a, y) == f(x, y, a) == ax + ay + xy
For all x and y. Is every number an identity of this function? In fact, this is trivially true for any completely associative function. I guess I just need to see the definition of an identity of a ternary operator.I really like this example, because it has an intuitive physical analog and is more tangible than than the example I typically reach for: lists.
I think the reason we don't usually use those definitions is we like to maintain the fiction we're working with actual integers or reals, which don't have a min/max.
all(xs + ys) == (all(xs) and all(ys))
And: xs == xs + []
We have: all(xs) == all(xs + []) == all(xs) and all([])
Which implies: all([]) == True.If this is controversial to any one at all, refer them to an introduction to logic course.
The only logical answer is yes. It would be rather absurd to make an exception for the case where you start with one marble in the bag. That would make logical reasoning extremely cumbersome, for no benefit whatsoever.
marbles = (BlackMarble() for _ in range(random.randint(0, 10)))
all(isinstance(m, BlackMarble) for m in marbles)
Surely should always be trueIf that case is so different, than the case with two marbles is also different - because removing the second-to-last marble will change will change the bag to be in that situation. But then, the case with three marbles is also different, and by induction you are left with only special cases.
On the other hand, see the related statement "If all marbles in a bag are black, adding a red marble will result in a mixed bag" clearly fails for the empty bag.
This isn't really a question of logic or mathematics. Mathematicians just use systems which are useful. And predicate logic, which has proven invaluable, would allow OP's claim.
> The only logical answer is yes.
The only "logical answer" is "there are no marbles in the bag". If this must be cast to a boolean somehow, the only "logical answer" is no. There should be a discontinuity as the last black marble is removed. That isn't absurd at all
A marble is in the bag -> the marble is black = True, even if there are no marbles in the bag. You can consider the "all" method as asking for the truth value of "A marble is in the bag -> the marble is black"
Also, why am I getting downvoted? I’m starting to realise that HN is not the place to come for intellectual discussion anymore if you have anything to say that isn’t immediate agreement. I guess dang has set a flag on my account that encourages others to downvote me, because I’m not sure what other explanation there is for how my comments have been treated recently
But is it useful?
Suppose I'm a bus driver and I ask "all aboard?"
I'd expect that if the answer is false, I'm missing somebody, and not that the bus is empty
The only logical answer is no. It would be rather absurd to make an exception for the case where you start with one marble in the bag... and yet:
marbles = ["black"]
marbles.pop()
all(m=="white" for m in marbles) # => TrueThe logical answer is "maybe."
A common error would be a programming mistake in the selection of the list element return an empty list. The returned list would, unexpectedly, comes empty. Trying to check if everything is black would return True and the code would follow on. It would be very hard to debug
If you want the inverse behaviour, you have to verify that all elements do not satisfy it. And that necessarily results in the empty set being falsy on any, but truthy on all.
Also, its just easier to use .any to check if the set is empty or not, in many cases.
But we are operating under predicate logic. The negation of “all marbles in the bag are black” is “there is a marble in the bag which is not black”. Then, clearly the latter statement is false in the case of an empty bag.
In fact, we can confidently claim falsity for any given statement of the form “there is a marble in the bag such that…” when the bag is empty. Therefore, by the law of the excluded middle, its negation is true, so statements of the form “all marbles in the bag are…” are vacuously true when the bag is empty.
It's certainly misleading in every day speech. On the bright side, all my papers have been published and all of my research funding has been granted.
What do you mean here?
"some element doesn't have the attribute" requires a non-empty set.
If you mean "it is false that some element has the attribute", then you need to write that very differently, which makes it much less of a surprise that it has different implications from the "all" statement.
The comment he replied to was claiming that any question that is true for n = 1 should also be true for n = 0, but this comment shows that it is obvious nonsense.
The comment above basically claimed:
marbles = ["black"]
all(m=="black" for m in marbles) # => True
marbles.pop()
all(m=="black" for m in marbles) # => True
If that is the case then the following would also need to be true: all(m=="white" for m in marbles) # => True
all(m=="green" for m in marbles) # => True
What color is the marble?Just because the question “there is a marble in the bag such that…” is false does not mean you can just negate it and say “all marbles in the bag are…” if the actual answer is that the question is not applicable.
"All marbles in the bag are white" is equivalent to "For all x, if x is a marble and x is in the bag, then x is white". Since the antecedent is false if the bag is empty, the statement is true by material implication.
"For all x, if x is a marble and x is in the bag, then x is white"
I would question if nothingness is a marble. Or would you say that the following statement is also true? I would say that x is a marble and therefore can be neither even or odd because it is not a number?
"For all x, if x is a marble and x is in the bag, then x is even"
That is just a nonsense question same as asking if all the non-existing marbles are of a particular color.
https://en.wikipedia.org/wiki/Vacuous_truth
Agreed, sometimes this leads to true statements that sound like nonsense. One example from the article is "if Tokyo is in France, then the Eiffel Tower is in Bolivia". This is a true statement because Tokyo is not in France.
>"For all x, if x is a marble and x is in the bag, then x is even"
We can rewrite this as "For all x, x is not a marble or x is not in the bag or x is even". We can see that this is true if "x is not in the bag" is true for all x. It's not really nonsense because we can quantify over all objects, so some of them will be marbles, some of them will be even, etc.
Going back to the more general question we can look at the statement "For all x, F(x)" where F is any predicate. Call this statement P. The negation ~P is equivalent to "There exists some x such that ~F(x)". But if we are quantifying over the empty set, ~P is false, so P must be true in that case.
all(m=="white" for m in marbles)
is not the same as "For all x, if x is a marble and x is in the bag, then x is white". The statement is more "For all x in the bag, is x white?" and the answer to that is either "No, nothing is not white" or "Question is not applicable".They're the same, just quantifying over different sets. I can quantify over objects in the universe, in which case I need the clause about being in the bag. Or I can quantify over objects in the bag, in which case I'm quantifying over the empty set.
all(m=="white" for m in marbles)
By De Morgan's laws, this is equivalent to
not any(m != "white" for m in marbles)
If you want all(m=="white" for m in marbles) to evaluate to False for the empty set, then any(m != "white" for m in marbles) has to evaluate to True for the empty set, or you have to give up on De Morgan's laws.
This is how standard first-order logic works. Again, you're welcome to try to make your own system of logic that works differently, but good luck doing that while preserving all of our theorems in set theory and mathematics.
- Take a bag for which “all marbles in this bag are black” is false
- Add a black marble to that bag
- After that, “all marbles in the bag are black” becomes true
As is often the case in mathematics/logic [1] with these kind of things, you can hold an alternative view where “are all marbles in this bag black” doesn’t make sense for an empty bag or one where it has the answer “yo”, but if you do that, you’ll find that many proofs get more cumbersome because you have to handle that case differently from all other cases.
[1] other examples are the convention that 0⁰ = 1 and the convention that 1 isn’t a prime number (that’s an example where mathematicians (eventually) decided that having 3 kinds of positive integers (prime numbers, composite numbers, and 1) is the better choice)
> - Take a bag for which “all marbles in this bag are black” is false
> - Add a black marble to that bag
> - After that, “all marbles in the bag are black” becomes true
This reasoning is not correct. If the bag contains exactly a red marble at the start, "all marbles in this bag are black" is false as you required, but it breaks the claim at the end that "all marbles in the bag are black".
There is in fact no bag that fulfills your reasoning, even the empty bag wouldn't (when starting to write my reply I had erroneously assumed so)
So it's consistent to say that a bag can contain all red and all black marbles at the same time?
As with anything mathematical, we picked the way that yields useful and elegant mathematics. The notion of "all" would be awkward to work with if it did not apply to the empty set (bags without marbles) so we extended it in a way that is consistent with what it means over non-empty sets (bags with marbles.)
One very nice thing about this definition of "all" is that it gives this simple relationship between logical "all" and logical "none":
"for all x, P(x) is true" is equivalent to "there is no x for which P(x) is false"
I.e., if there is no marble in the bag that is not black, then all marbles in the bag are black... so to speak.
This yields elegant and useful mathematics, and (as you would expect) that tends to mean elegant and useful code as well.
For example, suppose you had an enormous dataset of records that included zip codes and wanted to know: "Are all the zip codes in this dataset in the continental United States?" One approach would be to split the data into smaller shards, answer the question for each shard, and "and" all the answers together. What if you chose a bad way of sharding the data and one of the shards was empty? What result would you want returned for that shard?
Black is a property of a marble. Without a marble, black cannot evaluate to true. Therefore zero marbles equals “no”
Oh wait, nobody in the audience has a cell phone. Can the movie begin?
> Without a marble, black cannot evaluate to true. Therefore zero marbles equals “no”
"Is_Black" cannot evaluate to false either, therefore zero marbles equals "yes". But that's wrong too, because it is never evaluated, because there are no marbles to evaluate it on. TRUE is the identity of AND, and ALL is the aggregation of AND, so ALL([]) == TRUE. Put another way:
If you start from a bag with N black marbles and split it up between M bags, can you say that for each new bag, all the marbles in it must be black? Yes, you can. That is, if you find a marble in the bag, it will be black. This still holds even if one bag doesn't happen to get any marbles.
> Oh wait, nobody in the audience has a cell phone. Can the movie begin?
That's a really good one. Admittedly, I've never had to actually explain why statements can be vacuously true to anyone. But I'll keep this in mind if I ever have to.
"All marbles in the bag are black" is equivalent to "For all x, if x is a marble and x is in the bag, then x is black". Since the antecedent is false if the bag is empty, the statement is true by material implication.
[0] yes, “none()” doesn’t actually exist in Python, but we can imagine that it did, and see that if it did, it should have this relationship.)
“Apologies, we’re out of milk. Could I offer you a coffee without sugar instead?”
def all(L):
for i in L:
if not L:
return False
return True
Seems quite clear when presented that way. Same with `any`.> In logic, this is called the principle of bivalence: there are only two values that a proposition can have, true or false. But Python has None, many other programming languages have null or nil, and Zen has mu. Maybe adding another value would help?
If `all` were in Python v1 it would have thrown an exception when called with an empty list. (Said jokingly, or is it?)
The object isn't question isn't a statement of philosophical logic[1]. It's a computer program. And, yeah, it's implemented by iterating over the list and returning false if it finds anything false. If it gets to the end, it returns true. And that is all that needs to be said, because it is an algorithm and not a statement about the world. Maybe it's wrong. In which case, file a bug. But the bug isn't about philosophy either, it's about "I want to solve a problem and all() is giving me the wrong answer".
[1] Not to be confused with actual mathematics, of course. Math doesn't have semantic arguments like this because it starts from a stance of defining terms and axioms such that no one gets confused over words. Sigh, again.
return len(L) > 0
instead, which would obviously have different behavior. The code is the embodiment of a particular design, the "why", which has a more complex history.And I'm saying that's just plain dumb. This is an engineering discipline. If there's a "why", then the author of the code is the resource, not (sigh) philosophers. Go ask Guido, or check the commit messages. Don't ask Aristotle, he can't hack.
And the proof (that's right, proof) that I'm right is this: imagine Guido (or whoever) had indeed chosen the opposite convention for all(). It's easy to imagine. And it's equally easy to imagine people getting confused about it and asking about it. And you could reply with exactly this same article trying to explain the "why" behind it as part of two traditions of philosophy.
That's right: the same words explain the opposite convention too. That's not an explanation for "why", is it? This whole discussion is QUITE LITERALLY providing zero insight into the question you're imagining it's answering! It's like asking someone "Why did you paint your house blue?", and them answering "Well, you see, I could have painted it blue, or red."
For all x in X, P(x)
It's not arbitrary, and understanding formal logic and the history of formal logic is a good basis for understanding a great many things in computer science.
What the article explains is why the thing that produces the current result has the name “all()”.
While both can be answers to different senses of “Why does all() behave the way it does?”, they are fundamentally answers to very different questions that English, being ambiguous in the way natural language tends to be, allows to be expressed in the same words, the implementation explanation is not a clearer answer to the same question answered by the examination of the history of philosophy and predicate logic, and presenting it as if it were is fundamentally misunderstanding the question being answered by the longer piece.
The complement is also consistent. None of the set of zero elements are true. So there aren't any true's.
They're symmetrical cases, and negation turns them into each other. Not-any-true is equivalent to all-false, and not-all-true is equivalent to any-false.
Hoare labeled his invention of null a "billion dollar mistake". SQL NULL can't be far behind. Boolean logic has 4 unary and 16 binary operators, ternary logic has 27 and 19683, respectively. That's not helpful, that's intractable!
I cannot recommend C. J. Date's books enough.
SQL's NULL is far more useful though, since NULL != NULL. That lets you do things like join two tables with a column and not have the NULLs match.
Consider, for example:
select * from users where id = null;
That will give 0 results, even if somehow you have a user with a null id.It's also really nice for non-ID, non-reference fields to represent N/A. For example, user.birthdate can be NULL if the birthdate is not known.
SQL also lets you choose when you want NULL to be allowed for a column, so it's less likely to be a gotcha.
SELECT * FROM users WHERE status IS NOT DISTINCT FROM $xThat's not true! (Nor is it false.) NULL = NULL IS NULL, NULL != NULL IS NULL. I can't overstate this: as soon as either (or both) operand of any binary relational operator is NULL, the whole expression is NULL!
I'm not being an asshole, this is the exact mistake that makes SQL NULL the other billion dollar sink!
This is really funny, because i wasn't aware of laying such a beatiful trap.
(And it's not like that's some sort of mathematical nonsense either. It makes perfect sense to not create a special value that represent "outside my universe of values" and then refuse to equate any two of them. It makes perfect sense to declare a distinguished "outside my universe of values" and let it be equal to any other such value on the grounds that it is mathematically impossible to witness any differences between them anyhow so within the system they are indeed equal.)
create table people (
name text primary key,
age integer
);
insert into people (name, age) values ('Bob', 50), ('Alice', null), ('Mallory', null);
We know Bob is 50, but we don't know the age of Alice or Mallory.Are Bob and Alice the same age? We don't know.
Are Alice and Mallory the same age? We don't know.
If `null` were equal to `null,` we couldn't express missing information like this. Naive queries would still have bugs and conclude that Alice and Mallory were the same age.
Null is a property of living in an uncertain universe. When you start taking measurements of the real world (which every production business process is), you run into the many faces of null.
For example, I used to take data in a lab. Often we'd measure three replica samples. Sometimes I'd spill one of them. Scientific ethics demand I still write down the two measurements I did collect, even if I go get three more samples. For the third, I'd write the pen and paper equivalent of null (striking out that cell in the table).
Were any two of those missing measurements equal? Does a dog have Buddha nature?
I don't know what systems you're referring to, but like I mentioned you will get bugs if you naively use either approach. Eg in Rust, Option<T>::None will compare equal, because Rust doesn't have a null concept and that's how Rust enums work. But if you were using None to represent missing information, you'd want to use pattern matching rather than the built in equality. (Missing information is a much less salient concept in Rust than SQL, and Rust very deliberately wanted to avoid having a language-level null concept, so I think both of them made the right choices for their problem domains. Though I agree with this comment[1].)
But I'm getting the vibe that we're not going to convince one another, which is fine, this is an area where reasonable people disagree.
It shows that I haven't written Rust in a few months, that's not how Rust enums work, that's how the derive macro for PartialEq/Eq works. D'oh!
Isn't is a binary operator?
Because "NULL is NULL" is True (not NULL).
Why is it useful? Can you elaborate?
In that case it would be a bug if we matched with this errant user who's id is null.
It is in general very reasonable for a function to return an optional bool; think of `List.tryHead [false, true, false]`. What is unreasonable is for all boolean functions to return optional bools.
all = foldr and True
any = foldr or False"Re-joining the EU would give the UK all the benefits of EU membership as well as all the benefits of Brexit too."
So we read things about bags with no marbles and bearded kings of France and pink goblins and we think someone is taking the piss out of us, because we can't let go of the context behind those statements. Why are we talking about goblins at all?
All models are wrong. Some models are useful. If you're getting hung up on how it just doesn't make sense that "A -> B" is always true when A is false (which is really the same issue), you have to realize that it's not meant to be intuitive, it's meant to be useful. And the theory of predicate logic where ALL([]) == TRUE is much more useful than the one where it's FALSE or even NULL. It's more useful because TRUE is the identity element of the monoid AND and ALL is the unary set-aggregation of AND. There really cannot be any other choice if you want your theory to fit in with the rest of mathematics and logic, which a useful theory must. It's internally consistent, useful, and really does make deep sense if you are able to let go of context-laden real-world examples.
You should not rely on English sentences making sense to be a good metric of whether a model is useful or not. Consider:
Dogs must be carried on escalators.
You don't have a dog. Are you allowed on the escalator? Of course you are. This is, in fact, a perfect example of why Your_Dogs.All(is_carried) must be TRUE if Your_Dogs is empty. Hard hats must be worn in work areas.
You don't have a hard hat. Are you allowed in the work area? No! How can you tell the difference between these two situations? Only from context. Yet you can see how easy it would be to form the 2nd sentence as an (erroneous) example of "Enforce(HardHats.All(are_worn))", but it's not that. It's more like "Enforce(People.Where(in_work_areas).All(have_hard_hat_on))" -- if there is someone in a work area, then they must have a hard hat on. But if there's nobody in the work area, no rule is being broken! Again we actually see ANY([]) having to be true here.When you come up with weird-sounding examples where you're sure All([]) must be False, you're just poisoning your intuition with unhelpful context.
False->A
Since in this implication, we are starting from a false assumption, we have already left the guardrails of consistency. So we can say whatever we want.
False -> A
False -> not A, as well!
The simplest way to look at it is, having a false assumption is the same thing as saying:
Assume contradictions, then …
Or,
Assume (False = True), then …
Given a false assumption, anything can be implied.
Because if False equals True, then whether A is True or False, it is also the other.
—-
To take the antipode of feoren’s prosaic warnings against colorful context, let’s pile on context like it’s cake!
If we start in a false world, if the physics we live in are already a contradiction, then anything can happen!
Several impossible things before breakfast! Alice and wonderland!
You can have your cake (C) and eat it (not C) too!
0 is the additive identity
all([]) is True
any([]) is False
any([], predicate) => false
This is plainly obvious, because it is illogical to say "the predicate is true for at least one member of the empty set".If we can agree on that, then all([], predicate) must be true, because the complement of all(list, predicate) is any(list, predicate')
The naive assumption is that the complement of all(list, p) is all(list, p'), but this is demonstrably false, because in the case of an empty list: all([], e => e) and all([], e => !e) would both return the same value.
> [[], [true], [false]].map(s => s.every(e => e) == !s.every(e => !e))
[ false, true, true ]
Instead, its proper complement is any(list, p'), which means that all(list, p) = !any(list, p') > [[], [true], [false]].map(s => s.every(e => e) == !s.some(e => !e))
[ true, true, true ]
So, for all([], p) to be false, any([], p') would have to be true, which makes even less sense than all([], p) => true.If the quantified predicate were to be false for an empty set, then the above breaks if any of the partitions are empty. A property can then be true when quantified over a set such that it's not true over subsets, which is inconsistent.
A similar problem occurs with products. We can use Lisp:
(* 2 3 4) -> 24
We would like to be partition that into partial products. Here, we have a partition of length 1: (* (* 2 3) (* 4)) -> 24
So we want (* N) -> N. And empty partitions: (* (*) (* 2 3) (*) (* 4)) -> 24
For that to work we need: (*) -> 1 ∀x Fx
As Not ∃x (not Fx)
Ie: "for all x, f(x)" implies the opposite of "there exists an x where not F(x)".People seem to have less of a problem with
Not ∃x (not Fx)
when the universe is x is empty.I usually explain it to junior developers by telling them to imagine it's a for loop over a list that returns true/false if it finds a value that doesn't match what's expected, and the opposite of it reaches the end of the list.
> But it is interesting how even extremely practical twenty-first century programming can get drawn into millennia old philosophical controversies, intentionally or not.
This turns out to happen a lot. I can't find the source right now, but there was a good essay I read ages ago about how questions that are abstract and hand-waveable in day-to-day life become cornerstones of architecture rapidly in computer science and software engineering. The cow in the field epistemological puzzle [http://www.philosophical-investigations.org/2021/09/the-cow-...] maps directly to the nature of proving code is "correct" by testing (and any practicing coder has a story of false-positive or false-negative tests, as well as times they identified one of those by a "gut check" telling them "That result... doesn't feel right; I should investigate further"). Similarly, the philosophical question of "sameness" or "equality" ends up concretized over and over again in language design; there's a reason languages end up with so many ways to say 'equals', and it's because the underlying question of what it means for two things to be the same thing is legitimately philosophically complicated!
In college, my future ex-wife rented a video from the video store called “Kama Sutra” on my card and returned it late. The late fee was sent to my parents’ house. My dad discreetly told me later that it was not a problem that we got a late fee, which means he read the letter and thinks we had watched a porno together and was trying to let me know and be cool about it.
However, https://en.wikipedia.org/wiki/Kama_Sutra:_A_Tale_of_Love is a “historical erotic romance film” and not a porno. But my ex-wife and I did in fact see a different porno together.
So then, does my dad know that we saw a porno together?
As I sit here justifying my own reasoning, though, it sort of makes sense. For ordinary people (for whom this product is supposed to be for), I figure if they put in nothing, that was probably just a mistake.
> clj
Clojure 1.11.1
user=> (every? true? [])
true
user=> (every? false? [])
true
user=> (every? true? nil)
true
user=>
Also: user=> (true? [])
false
In English, an empty list is `false`, but any predicate on an empty list is `true`.So I believe the logic is the same as Python.
Logic article on this in Wikipedia: https://en.wikipedia.org/wiki/Vacuous_truth
def all(it):
for v in it:
if not v:
return False
return True
If there are no items, it returns True. def any(it):
for v in it:
if v:
return True
return False
If there are no items, it returns False.There's no need for fancy mathematical or "marbles in a bag" explanations here. Just look at the code.
Isn't this just a problem with an unclear definition of "having fur"? Is the article saying that the old logic would hand-wave this problem away because it's about "universals and essences"?
> “All stonemen are made of stone” is true by definition, but “no men are made of stone” is also true and seems to contradict it.
I don't see how “no men are made of stone” contradicts “All stonemen are made of stone”.
"stonemen" are not the same as "men".
English isn't my first language so maybe I don't know what a stoneman is (and google failed me), but I assumed it is a statue?
It comes down to a philosophy of logic issue. A universal quantifier ("for all x P(x)") is ambiguous when there are no x's. You can take it as false, true, or indeterminant, your system of logic will determine which interpretation should be used.
In modern predicate logic, that statement is true if there are no x's. In syllogistic logic it's considered false because there's a second presumption which must be satisfied: That there are any x's to talk about.
Q: All stonemen are ...
A: ERROR: Undefined operation. Stonemen do not exist.In colloquial use if you were to say, "All unicorns are blue" no one can meaningfully contradict nor confirm the statement, the statement is neither true nor false because there are no unicorns (this all assumes we're not discussing in a context where unicorns "exist", like about a particular fantasy setting or animated series). In syllogistic logic it becomes false because of the foundation of that logic (centered on reality, what exists and can be defined, not on fiction). Nothing true can be said about all of some non-existent thing because, well, it doesn't exist, so the statement must be false. In modern predicate logic it's considered true, because even though there are no unicorns we (someone) decided that it should be true by convention when the set is empty (turns out to have useful properties if we permit this).
These are just the rules that we've used for our logic, a choice we made. We're generally, in programming, operating under the modern logical rules, using neither colloquial rules nor syllogistic rules, so our programs are consistent with that last interpretation.
"All elephants in my pocket are made of dark matter". Sure, all _zero_ of them, so it's 'technically correct'.
https://en.wikipedia.org/wiki/Wikipedia:Skin?useskin=vector
https://en.wikipedia.org/wiki/Wikipedia:Skin?useskin=vector-...
https://en.wikipedia.org/wiki/Wikipedia:Skin?useskin=monoboo...
https://en.wikipedia.org/wiki/Wikipedia:Skin?useskin=timeles...
https://en.wikipedia.org/wiki/Wikipedia:Skin?useskin=minerva
https://fsharp.github.io/fsharp-core-docs/reference/fsharp-c...
Both implementations of all() and any() short-circuit by returning as soon as a fasly element in all or a truthy element in any is iterated over.
The origin of these two functions seems to be this post: https://www.artima.com/forums/flat.jsp?forum=106&thread=9819...
Guido makes no comment on empty iterables, but does comment on the final implementation needing to be efficient. It’s possible this behaviour is just the engineering trade off made for a slightly more efficient implementation.
The initial commit of these two functions - https://github.com/python/cpython/commit/96229b191814556622b... - is exactly as Guido’s suggestion, but does include a test case for empty iterables for both functions so we know it wasn’t overlooked.
But I think I have a more convincing example of why it can't be the other way around:
all (>5) [6,7,8]
all (>5) ([6,7,8] ++ [])
(all (>5) [6,7,8]) && (all (>5) [])
(True && False)
False if items and all(items):Similarly, the product of an empty list should be 1.
Such violations in software inevitably lead to bugs that could have been avoided. The real question in my mind isn't what the return value should be. The question is "How can this problem be wholly avoided?"
Some have suggested requiring a default return value when the iterator argument is empty. Others have suggested throwing an error. Perhaps others will suggest the all() function shouldn't exist, although that may be throwing the baby out with the bathwater.
[0] https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...
If
lst = [True, True]; all(lst) is True
is True, and lst = [True]; all(lst) is True
is True, I'd be very surprised by lst = []; all(lst) is False
Basically, all(lst) is the same as `not any(not item for item in lst)`. If we inverted the value of `all([])`, I'd argue that we'd also have to invert the value of `any([])`, which today evaluates as False. `any([]) is True` would be beyond bizarre to me.It's not the function that is wrong, it's those people.
[] exists. It happens to be empty, but it is neither a False nor a None. Therefore it should be a True if applying truthiness to it. IMO, any expression which does not result in an actual False or None should be judged as True.
- edit -
On second thought, all([]) -> True is also a poor choice, same in Ruby. The idea of judging an empty collection as having all items being any value is flawed, since there are no items to measure. It is on par with 1 / 0.
This is fun: in both Ruby and Python...
[].all? -> true
all([]) -> True
[].any? -> false
any([]) -> False
So both languages agree that all elements of any empty collection are true, but if you ask them if any of the elements are true, they deny it. So they are all true while also not having any true elements.
users = database.fetch_users()
if not users:
print("Didn't find anyone.")
and name = input()
if not name:
print("You didn't enter a name.")
Of course you could work around those like `if len(users) == 0` or `if not len(users)` or `if name != ""`, but each of those is uglier.`if not users` translates very nicely to English: "if there are not any users...". I'm glad Python defined `bool(list)` that way.
if my_container.empty():
# do stuff
than if not my_container:
# do stuff
The second implementation behaves the same if either my_container is None or if my_container is empty (or if my_container is 0 or ""...), whereas the first implementation allows me to handle those cases differently. The first implementation is also more explicit and easier to understand on a first read.It's also easier to debug. Is users supposed to be None in your example? Possibly, but your code doesn't give me any hints. Is my_container supposed to be None in my example? Probably not based on the way I explicitly used the .empty() method.
Really, though, list.empty() is canonically spelled “not list” in Python. That’s the idiom to learn, read, and write.
Not really. Those Python functions follow definitions from set theory and predicate logic, and in predicate logic e.g. "for all x, P(x)" is defined as being true if there are no x.
The reason for it being defined that way in mathematics is that it actually leads to consistent rules and definitions without the need to define lots of special cases, even if some of those definitions aren't how most people would use some of the same words in everyday language.
Similarly, the reason division by zero is considered undefined instead of e.g. equaling infinity is that if it were not considering undefined, that would lead to inconsistency. If we had 1 / 0 = a, with any valid result a, that would mean a * 0 would need to equal 1.
Programming languages could of course define their APIs to follow some other (hopefully internally consistent) logic, and I guess you could consider mathematical definitions based on predicate logic or set theory as being a somewhat arbitrary choice. But I disagree that the definition used in Python is on par with division by zero.
∀(head::tail) = head ∩ ∀(tail)
if ∀([]) is defined as true then this works for the single element list.But maybe that’s why I’m not a language developer.
for all x. P(x)
not (exists x. not P(x)) all([x]+x) > all([])
True
> any([])
False
Not very consistent philosophy in that case. Surely if 'all' is True then 'any' got to be too?No. `any([])` means "some element of [] is true", which is false because there aren't any elements. `all([])` means "all elements of [] are true", which is true because it's (classically, anyway) got to be true or false, and being false would require that there be some element of [] that isn't true, which there isn't.
More generally, `!all([!x for x in xs])` (or whatever the syntax is; I'm not a Python programmer)—meaning "it's not the case that the negation of every element of xs is true"—is `any(xs)`, so `!all([])` is `any([])`.
Some examples are "if something is true for 'all students', it should be true for any group of students", and if "all students have a hat" is true, then it should not be true that "any student had no hat", etc.
Any would be more formally: there is an element of the list which is true. But the list doesn't have any elements, so that's false.
Looks logical to me. Plus you get that `not any( map(lambda x: not x, l) )` is the same as `all(l)` for any list `l`.
It is just a matter of convention and I see no logic behind why any convention would be worse or wrong. But I believe if 'any' implies 'at least one' so should 'all' otherwise they use different conventions.
In practice I get it was arbitrary choices and this is just how you end up when doing the obvious implementation.
def all(l):
for e in l:
if not e:
return False
return True
def any(l):
for e in l:
if e:
return True
return FalseThe conventions they use are not an arbitrary choice, and are those of predicate logic. any() is the “for any” (existential quantification) operator, while all() is the “for all” (universal quantification) operator.
They are deeply connected: “all items in xs are true” is “there are not any items in xs that are not true”, or, in code terms, all(xs) == not any(not x for x in xs).
And, vice versa, any(xs) == not all(not x for x in xs)
In order to satisfy “does any x satisfy y” you need find an example that does.
You're logging incoming web requests and then gathering statistics in 5 minute intervals.
During one interval, you get no requests.
You check to see...did "all" of the requests get processed without errors? The answer is Yes, because to say No would imply there were errors.
Did "any" of the requests cause an error? The answer is No, because to say Yes would imply at least one error.
any (<5) [6,7,8]
any (<5) ([6,7,8] ++ [])
(any (<5) [6,7,8]) || (any (<5) [])
False || True
TrueI don’t care about any fancy philosophy. What’s important is what kind of operation you want for when implementing algorithms, and False is what I always want in cases where I do “all”.
There's no particular reasoning that this is any more compelling than: “Are there any items in this list that are false? No? Ok, then True”
Why would you want "all" to produce results that are inconsistent with logic and mathematics? Consider this tautology, which is a result of De Morgan's laws: for any list L, we have that
all(L) == ~any([~x for x in L]).
In other words, if all([]) == False, then you would also logically need to accept that:
False == all([]) == ~any([~x for x in []]) == ~any([])
which means that we'd require any([]) == True. And that's definitely an illogical result.