The 2,500 year old history of why Python’s all([]) returns True
blog.carlmjohnson.net
blog.carlmjohnson.net
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.That said, I do think there is something to the perspective presented in the article. Something which is not accidental to the algebraization of logic...
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....
a && True = a
a * i = a
the operation (denoted * above) is &&, and the identity (denoted i above) is True. Lets see how it holds out for all possible values of a: a && True =?= a
let a := True
True && True =?= True
correct!
a && True =?= a
let a := False
False && True =?= False
correct!The correct term is an identity.
According to the Curry-Howard correspondence and the BHK interpretation of logic, there is a syntactic correspondence between types and logical formulas and between terms and proofs.
Here, the unit type corresponds with truth because it is trivially inhabited with a single inhabitant. The product type A * B is interpreted as logical conjunction because you need a proof of A and a proof of B to inhabit it.
The unit type (true) is the identity of the product type (and). Unit * A = A * Unit = A from an isomorphism perspective. From the category theory point of view, a Cartesian monoidal category is a monoidal category where the tensor product is the categorical product and the unit object is the terminal object. It's no coincidence that the terminal object is written as 1.
So, an n-ary sequence of propositions connected by ANDs is like an n-ary tuple type, and an empty sequence of propositions connected by ANDs is like the nullary tuple type, or in other words the trivially inhabited unit type.
ɪ, don't know.
We want this general property to hold:
sum([ sum(x[0:n]), sum(x[n:]) ]) == sum(x)
define sum([]) (n = 0) so that it holds
It works the same for prod, all, and any, and gives a hint as to what the answer should be if there were things like union(sets) (the empty set) or intersection(sets) (the universe, or union of all sets)
prod([2,2]) = 2*2 = 4
Right?Edit: could have picked better examples too imo... Id have used 1 + 0 = 1 and 2 × 1 = 2... basically you add 1s for free when multiplying, 0s for free when adding, and trues for free when "Alling".
if(all(requirements)) { proceed; }
If there are no requirements that need to be satisfied, then you should be able to proceed.Yes, precisely the point of the article. We mostly find it natural to think like moderns today, but civilization went along for thousands of years thinking differently.
def all(iterable):
for element in iterable:
if not element:
return False
return True
https://github.com/python/cpython/blob/master/Python/bltinmo...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.
That wasn't necessarily a choice, it may have just been an oversight not to deal with the "empty list" case.
The fact that this blog post exists is a testament to this behavior being surprising.
I don't think any of the arguments - philosophical or otherwise - are very good from a practical standpoint, that is: is this behavior is not more likely to cause harm?
My gut feeling says that this behavior is somewhat unsafe, but then again Python is probably not the language to use when safety is a big concern.
A hypothetical safer language might be better off to define "all/any" only for non-empty list.
> That wasn't necessarily a choice, it may have just been an oversight not to deal with the "empty list" case.
If your general code gives you a answer for a corner case, it's a good hint that this might be a reasonable answer. Not a guarantee, though.
Such code is less likely to contain obfuscated logical errors arising from an attempt to not break "functional purity". It has a chance to be practically more safe.
Another aspect is the way most functional languages treat memory. If you need a garbage collector, you effectively have to throw out hard realtime constraints. Latency and memory usage can be safety concerns. Most functional languages do not let you reason about that.
Most languages of any kind do not let you reason about that. You're not wrong, but neither is this some specific issue with functional languages. Indeed I'd say functional languages are at the forefront of trying to bring in ways to reason about those, with things like Linear Haskell or https://www.microsoft.com/en-us/research/publication/coq-wor...
I might as well generalize my statement to:
"Programming languages tend to elevate some ideals before concerns of practical safety, performance or just common sense."
It just wouldn't make as much sense in-context.
That statement would be difficult to substantiate, particularly in the context of functional languages. Practical safety, performance, and common sense are absolutely priorities; the ideals that functional language design follows are servants of those goals, not the master.
Garbage collectors are used in most modern languages. Most implementations of garbage collectors don't give any real time guarantees. And neither do typical free/malloc implementations.
You can do functional programming without garbage collectors. Eg via linear types / uniqueness typing.
But the more general answer here is: whether your tool is appropriate depends on your problem.
Hard real time constraints are rare for most software. Most languages in widespread use don't support those out-of-the-box. Eg C certainly doesn't.
> It boils down to the position that side-effects are actually totally fine when they allow you to write a simpler, more straightforward program, where state is easy to observe and debug.
You know that eg Haskell allows you to write imperative code just fine? It's actually one of the more pleasant languages for imperative programming.
And it's pretty easy to get proponents of FP to admit that imperative programming has its uses. Much easier at least, than to get them to say positive things about Java-style OOP.
> Such code is less likely to contain obfuscated logical errors arising from an attempt to not break "functional purity". It has a chance to be practically more safe.
As I said most functional languages support imperative programming styles just fine. But, of course, your complaint remains more valid if you are redirecting it against certain functional programmers. Languages are tools to be used by people. Some of them more flexible, some more boneheaded.
If you think of "all" as "in this set, no element exists which is False" then it is "obviously" the right behavior.
However, consider this more practical example:
x = {1,2,3}
y = {}
all(e in x for e in y)
> True
This doesn't strike me as obviously right.If it did look right to me, I wouldn't be sitting here typing this all out. I would be silently agreeing with the mathematical point of view.
That's what tells me this is a likely source of confusion and error.
Is there any language that works the way you’re suggesting is more practical/intuitive?
For practical reasons, it might be better for it to be false. That would require some empirical research on whether this could actually prevent harmful behavior in practice.
From a design perspective, I would argue that "all" should not be defined for empty lists, because that forces people to consider whether the often-forgotten "empty list" edge case might cause an issue.
We invented mathematics because practical thinking leaves us without answers at some points. You are free to axiomatically choose "all([]) = false", but then you'll find out that many mathematical equivalences won't hold as they all assume only the ZF axioms.
The consequence of "if (True)" is usually to do something, not nothing. The consequence of doing something by overlooking the "empty list" special case may be harmful.
Preventing potential harm is more important than not having the programmer jump through a couple of extra hoops to account for the special case. That makes the special case impossible to overlook.
This kind of reasoning is generally accepted when talking about implicitly nullable types vs explicitly nullable types (optional types), but somehow in this context people are more stubborn.
Not sure that's the same? Haskell has no implicitly nullable types. Everything not explicitly marked as optional is non-nullable.
But still, if your algorithm at hand can treat nulls the same as non-nulls, it's a good idea to do so. (Eg in a sorting algorithm where you just feed the elements to the user supplied comparison function, and let that function handle comparing of optional elements.)
all([True, True] + []) is equivalent to `True and True and ?`. If you replace ? with False then all() will never return True in any case because given any list you can always create an equivalent list by appending [] which would taint the list to always return False on all(). Obviously nobody wants all() to return False on a valid list and the concept of tainting a list is pretty stupid so True is the only option. If you replace ? with True you can add as many empty lists as you like which is the desired behavior.
all([True, True] + [] + [] + []) == True and True and True and True and True
If it was obvious from any standpoint, we obviously wouldn't be having this blog post or this discussion thread.
Natural language - the natural way to think about things - does not always intuitively translate over to predicate logic. If you disagree with that, you should try teaching it to a class of high schoolers.
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). (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 == 7But whenever I've tried to explain things like this to people, I've found so many just don't seem to see the issue, or to remotely care if they do. Their intuitive responses tend to be:
- It's an obscure issue, it's never gonna come up.
- Oh, well I mean it's obviously your fault for calling it that way with that implicit assumption. Why would you think that?
- Well it's the caller's responsibility to read the documentation/look at the source/test the code before they use this function, not my problem to try to fit it into every mathematically possible use case.
I feel like the notion of putting some more thought into what seems like an elementary API so that these issues don't come up and trip up users in the first place seems like a completely unjustifiable academic effort to them, if not an outright foreign one. Have you found any effective way to try to communicate stuff like this and hopefully actually convince people?
Or let's say you're sorting strings case-insensitively. "a" might be neither less than nor greater than "A", meaning they'd be unordered with respect to each other. But that doesn't mean you suddenly wouldn't care if one got duplicated and the other got deleted.
Fundamentally "unordered" just means "neither comes before nor comes after". It doesn't imply "equal to". You can wrap this in a lot of fancy math terminology about partial and total orders, but it's not that complicated conceptually; the idea is just that the lack of an ordering doesn't imply equality, just like how you and your twin aren't the same person. This is one aspect of why C++ is introducing more sophisticated comparisons in C++20 (I would suggest looking it up).
They have the same age, which might be the minimum age of the group. I don't think it makes sense to talk about the minimum person of the group; minimum/maximum imply that it's the thing itself which is ordered.
(Even more concretely consider a min heap used in pathfinding, where you store the (distance_so_far, (x,y)) pairs, and they sort by distance_so_far.)
Sure, but again, at that point you're not taking the minimum thing, you're taking the thing with the minimum priority. And it wouldn't be strange to say that two things have the same priority (even if they're different things).
Uh, it totally makes sense, I just gave you a string example too. The comparator doesn't have to distinguish unequal elements, and a min (and argmin and sort et al.) is perfectly fine and sensible with such a comparator.
Do you code in Python at all? Do you find the fact that 1.0 and 1 are both simultaneously equal in some sense and unequal in another sense to be just complete nonsense? Do you expect min(1.0, 1) to error...? Or for sorted([1.0, 1]) to return [1, 1] just because the elements are "equal" so obviously what right do you have to care which one is returned?
Sorting is not the same thing as taking the minimum.
> Do you code in Python at all? Do you find the fact that 1.0 and 1 are both simultaneously equal in some sense and unequal in another sense to be just complete nonsense? Do you expect min(1.0, 1) to error...?
Yes, and this is a large reason why I prefer to work in languages that let me manage such distinctions more carefully.
https://hackage.haskell.org/package/base-4.12.0.0/docs/GHC-L...
https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-...
The point about strings is different. It doesn't make sense in general to speak of the minimum string in a list, or, more generally, the minimum of a partially ordered set; but it does make perfectly good sense to sort a partially ordered set, with the idea that there are multiple correct sorts when elements are incomparable. That, of course, is where the notion of a stable sort becomes important.
In [1]: l = [(19, "John"), (19, "Jack"), (20, "Jim")]
In [2]: min(l, key=lambda x: x[0])
Out[2]: (19, 'John')If you've got a list of people that is already sorted by name, and you want to sort them by age but still preserve the name sorting for each age (ie, if Alice and Bob are both 30 years old, I still want Alice to appear before Bob in the list), then having a defined way to break ties greatly simplifies the code.
You can make 1/0 return infinity, and preserve some properties of fields. Though that works best, if your zeroes are also signed.
But for 0/0, I don't see nearly as many properties you can rescue.
If, on the other hand, you regard y = 0/0 as a "multi-valued variable" standing for any element of the field, then it behaves perfectly fine, since 0y = 0. The only problem is that it's infectious, so that just about any arithmetic computation involving it, like y + 1 or 2y, also suddenly has to stand for every element of your field. (If you work over a ring, then y + 1 stands for any element and 2y only stands for elements of the ideal generated by 2, etc.) Computations like 0y and y + (-y) both still yield 0.
This is not very useful—at least I can't see anything useful to come of it—but, aside from the infectious multi-valuedness, I don't see what problems result.
It includes a test for the empty case: `self.assertEqual(all([]), True)`. They knew what they were doing.
When observed planetary motions were found to be incompatible with the assumption, epicycles were introduced to keep the initial assumption working.
Are you my Intro to Operating Systems professor? Because he would take points away from answers on the exams for being more long-winded than necessary, even if the answer was correct.
Joking aside, your perspective is bizarre to me. You haven't offered any reasons to propose that all([]) should be False, but reject the given answers for it being True simply on the basis that the explanations are long-winded?
`all(x) == not any([not v for v in x])`
so `all([]) == not any([])` -> `True`
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.
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.
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.
I can’t actually think of a coherent argument for returning anything besides true.
"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.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.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.Common folk tend to disagree about whether zero is an even number. Calculating 0 mod 2 is a confusing situation, and no numeric answer we could give would satisfy the principle of least surprise, so we should throw an error.
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.
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.
I do think most programmers would benefit a lot from learning the fundamentals of logic, or at the very least boolean algebra. after seeing some of the tortured expressions they write, I certainly wish my coworkers were more familiar with boolean simplification rules and identities.
I've lost count of the amount of times I've factored out the "not" from horrid expressions in my colleagues' code like "not this and not that" using Dr Morgan's law. Funny thing is I learnt Dr Morgan's in electronics a long time before I got to computers.
I think the problem is it's "boring" and people get hooked on doing stuff with programming. I can't blame them. But I wish more people remembered the fundamentals.
Lewis Carroll didn't know this, and treated universally quantified statements over an empty domain as false. Of course that's wrong; but I think it's over-specialising to claim that this is something obvious, or that everyone knows.
(Also, as others have said, if someone learns from this, then that's great! That's a positive impact, whereas saying "you should have known it already" is purely negative, so why bother?)
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.
Your example ""all stonemen are made of stone" would be true under both modern and supposed Greek interpretations.
Excellent point, thanks!
>>> 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.)
But then again, what can you expect from a language that uses "+", the quintessential notation for commutative operations, for the (very non-commutative) operation of concatenation?
Doubly sad when you realize that the language was designed by an actual mathematician.
This is true, but they do not have exactly the same meaning. On a poset you do not speak of "the min", but of "a min". You still have "the least", which is a different notion.
Is this any worse than using `+` for floating-point addition, which isn't even associative?
Even if it wasn't, floating point numbers are very often regarded as approximations of real numbers, where addition is associative. String concatenation cannot be construed to have any resemblance of commutativity.
This is just absolutely not true. It is certainly "min" and "max" for any total order, they definitely don't have to be numbers or even numbers with the usual ordering (as confusing as that would be with a set of numbers but the reverse ordering). "first" and "last"? I've never heard that.
I haven't worked with partial orders so much that I can be as confident about that, perhaps "least" and "greatest" are the correct terms. But "min" and "max" are certainly used sometimes by non-specialists (i.e. professional mathmaticians that don't necessarily spend a lot of time with posets) without any confusion.
'a' < 'b' only by accident of history. It could just as easily have been the reverse.
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.
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
> 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.
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.
Similarly we could have said b < a is true but then it would always be true and a < b would be false and the world would be exactly the same because this is all a made up convention for the benefit of humans.
Numbers are the underlying conceptual thing.
To get what you want, use min(lst, default=-math.inf) .
In Python 2, you had
float('-inf') < ""
So you could sort of get away with that. But that positive infinity wasn't bigger than any string. And in Python 3 comparing strings and numbers just results in an error.(infinity + 1) > infinity
which it isn't. I know about IEEE infs and nans, and neither are numbers, just useful conventions.
Agreed, neet to be precise here, so let's de-abstract away from numbers. Which is greater, 3 apples or 8 apples?
Now give me infinity apples. Finite apples exist, infinite apples don't except as an idea. Personally, infinities make me uncomfortable.
As for mathematical notions of numbers, there are of course mathematics with all kinds of infinite numbers. You have the transfinite ordinal and cardinal numbers, with the famous 2^Aleph0 = Aleph1 value (2^the number of natural numbers = the number of real numbers). You have infinitesimals as well. These are all just as well defined as the naturals and rationals, and exist just as much.
Not to mention that you could say: well sure, I can imagine a trillion trillion apples, but they don't really exist, they're just a fantasy. In reality we only have MAX_REAL_APPLE apples, and damn it, Johnny just ate one so the largest 'really existing' number just went down.
Overall, we can play this game a lot, but the reality is that pi is as real as Aleph0 which is as real as 1 and 2 and 0.0000...1 and +Inf etc.
Sure. So a simplified model is probably the right tradeoff to make. But we should not forget that it is a tradeoff; by working in a simplified world of notation we run the risk of forming constructions that can't actually be carried back to the real world of counting apples or dust mites. The map is not the territory.
> Overall, we can play this game a lot, but the reality is that pi is as real as Aleph0 which is as real as 1 and 2 and 0.0000...1 and +Inf etc.
"All models are wrong, but some are useful" - but not all models are equally wrong, and not all are equally useful. Including pi gives us a model that's easier to work with, but introduces a certain risk of forming a construction that doesn't translate back to reality. Including infinities brings a substantially bigger version of that risk. It may still be the right choice, but it's a choice that should be made carefully and deliberately; blindly assuming that all mathematical models are appropriate to a given situation is quite unwise.
i know this is nitpicking, but i think when you really look at it, "infinity apples" isn't that much more of an "idea" than "3 apples". (even if the latter is more useful in day-to-day life)
You could invent a monoid instance of `float` for `<` which includes `inf` as an empty element and, if you ignore NaN, it kinda sorta works in isolation. However, adding infinity breaks a lot of other nice properties to the numbering system.
For example, the following identity of `min` is valid in the finite numbers.
``` n < min A ⟺ (∀ a ∈ A) n < a ```
The right part of the equation is always true if `A = ∅`, and according to our new monoid the left part is true if and only if `n ≠ ∞`.
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.
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.
> (and)
#t
> (or)
#f
> (+)
0
And most surprising (although it's very convenient on second thought): > (* )
1
Thus multiplication on empty list gives 1.1 * x = x
0 + x = x
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.
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.
Hard reality does not always agree.
Nor do HN moderators, apparently.
But when you are building arbitrary systems, some design choices give you simpler and more applicable systems than others.
Why Swift specifically? This would be in the calling signature, implying there are inputs you can give said method that are nonsensical.
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?)
Indeed they aren't! All unicorns are not blue.
Over the past few decades I’ve seen ten of thousands of non-blue things, and none of them were unicorns!
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.
The surprising thing is that all unicorns are red, too.
all(x + [True]) == all(x)
any(x + [False]) == any(x)
all(x + y) == (all(x) and all(y))
any(x + y) == (any(x) or any(y))
not all(x) == any(not xi for xi in x)
not any(x) == all(not xi for xi in x)
Any other values for all([]) and any([]) would cause some or all of these theorems to be correct for all values of x and y except the empty list. In practice this would mean you would have a bug in your program on most occasions where the argument of one of these functions was empty, unless you included a special case for empty sequences.For example, suppose you're processing a request that includes some tasks and some attachments. You might write something like this:
if any(task.is_failed() for task in request.tasks):
raise TaskFailed(request)
if any(x.is_too_large() for x in request.attachments):
raise AttachmentTooLarge(request)
Clearly for this code to do the right thing in the case where a request has tasks or attachments but not both, we need any([]) to be false. And the corresponding requirement applies to all([]) if our task interface is a little different: if not all(task.is_succeeded() for task in request.tasks):
raise TaskFailed(request)
Now, there are occasionally programs where you do need a special case for empty sequences; for example, if you have three search results, you might want to return a page containing just those results, but if you have zero, you probably don't want to just return an empty page — the user might think the software is just broken. And in the above example, if there are neither any tasks nor any attachments, it might or might not be useful to report that the user accidentally submitted an empty request. But it is unusual for such a special case to just fall out of a possible alternative semantics of all() and any().True is the identity element for And, similar to how One is the identity element for multiplication.
> all: "there is none in the array that is false"
You... are aware that those statements are exactly the same, right?
See e.g. https://en.wikipedia.org/wiki/Universal_quantification#Negat...
I wouldn't have thought so personally, but alas, here we are ;)
The common definitions for 'all' and 'any' as used in math are certainly on thaumasiotes' side. But English as a natural, every-day language is a bit more fuzzy.
"all in the array are true" is not specific about how the empty case ought to be interpreted. That's not at all to say all([]) ought to be False; the wording is simply under-specified.
Where as "there is none in the array that is false" is clearly True when all([]) is encountered.
Thus the statement cannot be simplified to the English statement "all in the array are true", without introducing ambiguities.
As a general rule, this is why we don't perform mathematics (or programming) in English.
EDIT: Just to clarify why this is ambiguous, in case anyone reading is still not following (although I'd suggest reading the article!)
English is an (informally) consensus based language. Understanding boolean logic is not a precursor to being able to interpret common language, in particular English. Just because one has knowledge of logic (mathematics) and we happen to have given some operation the English name "all", that does not mean the English word "all" in general speech refers to this logical operation. Especially since, as the article describes, the mathematical consensus for this logic has only largely shifted to be interpreted this way in the last several hundred years. The English word "all" predates the current mathematical consensus (which isn't even universal) by thousands of years.
Additionally, if for some reason you are interpreting the written word "all" (no matter the context) as the logical operation, then "all in the array are true" is not a viable definition for this word, it's self-referential and thus ambiguous. "there is none in the array that is false" on the other hand is clear in its meaning (if not slightly grammatically incorrect).
As for eg Python: when in doubt, going with the mathematical usage of the term is the Right Choice here.
For another interesting example: the negation of must and may in English vs their German equivalents.
In English 'You must not do X.' means that refraining from X is obligatory. In German the equivalent 'Du musst nicht X-en.' means that engaging in X is not obligatory.
Both usages make sense in their respective languages. And both usages have translations into formal logic. Just different ones.
This is at the edge of possibility, but it's certainly not a slam dunk. "All" is not an indo-european word; it's restricted to Germanic languages.
> Just because one has knowledge of logic (mathematics) and we happen to have given some operation the English name "all", that does not mean the English word "all" in general speech refers to this logical operation.
This isn't a good argument; all you and the article are saying is that people have vocally objected to the meaning of "all" for a long time. They still do. But this isn't sufficient to claim that the objections reflect a disagreement over the meaning of "all". If you carefully explain to people how you want them to interpret sentences like "every cat is in a box", and then you show them pictures and ask them whether or not every cat is in a box, most people will screw it up. That's not because they didn't understand you; it's because they are very bad at the task.
It is quite clear that the vernacular meaning of the English word "all" coincides with the formal meaning of the logical universal quantifier. You can poll any number of people and they'll give you a definition that just so happens to match the logical quantifier. People don't object to all([]) being true after thinking about what "all" means to them -- they object before thinking about it.
I doubt it'll match formal logic.
It's not important to the discussion at hand, but I enjoyed your wording here, because even though you're right, in a sense this is one of the most controversial points in mathematics: https://en.m.wikipedia.org/wiki/Constructivism_(philosophy_o...
∀x.P(x) is obviously true when the universe is empty -- no counterexamples exist. ∃x.P(x) is obviously not true in that case -- no examples exist.
But eg Python's minimum and maximum functions don't do that. They throw an error by default.
In mathematical terms, if you have a lattice https://en.wikipedia.org/wiki/Lattice_(order) you can always just arbitrarily introduce a least and greatest element, and all the relevant laws stay valid.
Whether that's a good idea for your application or not, is a different question.
In programming, types often give you trouble. Eg the integers don't have least or greatest element, but you want your minimum function over list of integers to return an integer. So throwing an error is the only choice, if you can't represent something like minus infinity in your return type.
You can define infimum and supremum that way. But I was under the impression that wherever the concept of a "minimum element" of a set is used, the minimum element of any set must be an element of that set.
So e.g. the positive reals have an infimum, 0, but no minimum. The page you link doesn't do much to dispute this idea; it uses "minimum" and "maximum" to talk about the largest and smallest elements of the lattice, but never to talk about a property that a subset of the lattice might have.
Of course, the Python function named 'min' could very well implement an infimum in this case, and it would be OK.
In general, there's some conventions, like using the words 'minimum' vs 'infimum'. Or stressing the property 'result of min should be an element of the input set' vs 'min on lists should behave like repeated application of min on two arguments starting with the neutral element'.
The right choice depends on context. In lots of programming context, I find stressing the connection with the minimum monoid was useful.
Using min(∅) = +inf and max(∅) = -inf would violate the otherwise valid constraint that min(x) ≤ max(x). I'd be a little uncomfortable with it for that reason.
> The right choice depends on context.
Definitely.
Excellent point!
Funny enough, we have the same with 'any' and 'all':
For most lists of booleans: all(l) ≤ any(l). Or equivalently all(l) implies any(l).
But that property is broken for empty lists.
Hmm, I think I might understand the case for `all([]) == False` a bit better now. Though I still disagree with it.