Hacker News new | ask | show | jobs
by gridlockd 2284 days ago
> But that doesn't tell you _why_ that choice is a good idea!

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.

2 comments

Haskell, the prototypical careful language, defines their equivalents for 'any' and 'all' exactly like Python does it. And that's not an accident.

> 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.

Functional languages tend to elevate some mathematical ideals before concerns of practical safety, performance or just common sense.
On my cynical days, I'd be inclined to grant the latter two. But do you have an example of the first?
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.

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.

Thanks for the answer!

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.

> 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...

> Most languages of any kind do not let you reason about that.

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.

Maybe it's because I studied mathematics, but to me the `all([])==True` seems so obviously true and logical, and that any other behavior would be extremely surprising, and unsafe, for the opposite reason.
It's pretty obvious from any standpoint.

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

> It's pretty obvious from any standpoint.

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.

Lol, I used to believe that logic was an intrinsic faculty of human reason, and then I taught a bunch of freshmen.
It probably is because you studied mathematics.

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.
That's mathematically equivalent to "y is a subset of x", which is obviously right. (Except for the wrinkle that y is a dict, not a set.)
Replying to the sibling comment,

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.

I don't disagree. Try to mute your "mathematical thinking" for a second and read it as an ordinary person, with a focus on practicality. It just doesn't look 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.

I simply don’t have the intuition, at all, no matter how I think about it, that it should be false.

Is there any language that works the way you’re suggesting is more practical/intuitive?

I'm not saying it should be false. It's the law of the excluded middle that forces us to make a true/false decision, in which case I can agree that, for mathematical reasons, it should be true.

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.

It's the right choice for practical reasons as well as theoretical/mathematical ones. It means that you generally don't have to special case the empty list case, you can just run let it happily do nothing. If you want exceptional behavior then you should probably use some other value (e.g. None)
> It means that you generally don't have to special case the empty list case, you can just run let it happily do nothing.

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.

> 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.)

Can you give a practical example of a case where that answer would actually be wrong for an empty set, though? Like, what are you trying to check, and what would be inside the "if"?