def all(iterable):
for element in iterable:
if not element:
return False
return True
https://github.com/python/cpython/blob/master/Python/bltinmo... 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?