Add another allSatisfy function that has parameter for empty

Well the point is that there are no valid use cases for which allSatisfy should return false for empty collections. That would be in conflict with logic and language itself. That would be like suggesting that !!true should be false because in some natural languages double negation is used as intensifier for a single negation.

Actually the usecase mentioned above (requests and jobs finished) is not expressed correctly:

self.data.allSatisfy { self.requestURLS.allKeys.contains($0.key) }

This expression checks if all data that has been received belongs to one of the job URLs. But actually one would want to check if for all job URLs the data has already been received:

self.requestURLS.allSatisfy { self.data.allKeys.contains($0.key) }

Then allSatisfy would work just fine. The key is that from a logical perspective contains is an existence quantor and allSatisfy is an all quantor and the meaning of expressons changes if their nesting is reversed. [a popular example is: "For all numbers it exists another number which is greater"(true) vs. "There is a number that is greater than all other numbers"(false)]

So the issue is not with allSatisfy but with expressing domain logic via flawed logic.

3 Likes
func job_is_healthy(name: String) -> Bool {
  return running_jobs.filter { $0.name == name }.allSatisfy { $0.healthy }
}

The job not having any running instances is not healthy, yet the above would say it is. It’s not written correctly in the sense that it doesn’t do what the author intended, but I hope you can see that it’s a very understandable attempt. Beyond that, how can you rewrite it to be correct while also remaining as terse and readable?

I don’t think anyone’s arguing that the behaviour of allSatisfy should be changed, they’re just suggesting it would be nice to have a version that behaves differentially for the special case of empty collections.

There are other ways to write job_is_healthy, both with the exact behaviour of the above code and with the intended behaviour. I’m not sure that’s relevant either way - I don’t think Swift tries to have exactly and only one way to do any given thing. I like to think it tries to make programming accessible to a wide audience, which is helped by making it possible to do things in many reasonable ways, to meet with many different people’s intuitions and ideas about how to accomplish a task. Pragmatism in balance with, if not over, idealism.

I think a more persuasive line of argument, from those that are opposed to any library changes to support different treatment of empty sets, would be to try to show how it detracts from the existing library API in some way. Otherwise, the counterarguments seem to be essentially just “I don’t like this and/or I don’t think I’ll use this, so I don’t think anyone should have it”.

Beyond that, I suppose it’s a philosophical debate as to how inclusive specifically the standard library should be. I myself like a comprehensive toolkit, and that’s much more beginner friendly - witness the love for Python’s relatively exhaustive standard library.

I am sorry but your new example is still incorrect, now even more. Translated back to natural languag it expresses the question:

Are all jobs named X in a healthy state?

If there is no job with name X then the answer is cleary yes. If you do not want it to be yes in this case then you are simply asking the wrong question. But not the wrong question in terms of swift methods but the wrong question even in natural language.

It's expected that logic reasoning abstracts from the specific content of an expression and only cares about the logical structure. Actually that's the whole point of logic in general. So in a sentence as above it is required by logic that you can replace words as "jobs", "name" or "health" with any other words of any domain an still get the same answers for all imaginable cases. For example

Are all numbers divisible X also devisible by 1?

(I replaced "jobs" with "numbers", "named" by "divisible" and "healthy" by "divisible by one")

Clearly the answer should always be true because all numbers a divisible by 1. Even all numbers divisible by 0 are divisible by 1, even though actually no number is divisible by zero.

The key is that the answer to such a logic question does never depend on your specific domain. From a logic perspective it does not matter if you are talking about jobs, requests or numbers.

Maybe one could imagine a method collectionNotEmptyAndAllSatisfy that could be used to express your domain problem. But for me the issue seems to be that your problem statement is already wrong in natural language. Your method name is_job_healthy already indicates that we are always talking about a specific job and not about possibly multiple jobs of the same name. So the natural language question to ask if a job named X being healthy would be:

Does there exist a job in the collection of all jobs whose name is X and is it's status healthy?

or

Does there exist a job in the collection of all jobs whose name is X and whose status is healthy?

So you can see that the logic structure needs an existence quantor, not an all quantor and a logical conjections (&&). So translated back to swift your function should look like:

func job_is_healthy(name: String) -> Bool {
  return running_jobs.containsWhere { $0.name == name && $0.healthy }
}

containsWhere corresponds to the existence quantor and is the opposite of allSatisfy.

Now if you indeed want to support multiple jobs with the same name and check for all to be healthy than you would write:

func health_check(name: String) -> Bool {
  return running_jobs.containsWhere { $0.name == name } && 
     running_jobs.filter { $0.name == name }.allSatisfy { $0.healthy }
}

Natural language is not always very precise, so it's common to mix up existence and all quantors. But the key is to chose the right operators. In all given examples simply the term all was used incorrectly and there is no way to change that by adding more options. Both all quantor and existence quantor are available. Maybe names like all and exist or every and some would be clearer, but you would still have to chose the right one to express what you really want.

There is a similar example for addition and substraction with numbers in form of a riddle:

you see a shirt for 97$ so you borrow 50$ from your mom and 50$ from your dad. you get 3 dollars back so you give 1 to your mom, 1 to your dad, and keep 1 for yourself. now you owe your mom 49$ and your dad 49$. 49$ + 49$= 98$ plus your 1$= 99$.... what happened to the other 1$.?

The solution is that the way the riddle is stated it mixes up dept and assets. Instead of 98+1 would has to to 98-1 to get back the 97$ price of the shirt. Adding 1 to 98 is meaningless in that context.

At first glance it seems that plus and minus are broken but then you see you just apply them in the wrong way. It's the same with logic. If you think the all quantor (allSatisfy) give the wrong answer then you are using it in the wrong place and probably need an existence quantor (containsWhere)

4 Likes

The thing is, it is not about someone like this or not, it is about math, it is very explicitly saying that empty set has that property. You wouldn’t add additional parameters for addition or multiplication because “default” behavior doesn’t serve needs of specific domain, would you?

Actually, we had very similar discussion in our team, it was about combining futures (think of it as async value). There was a function that takes an array of futures and produces futures of array:

func waitForAll(_ fs: [Future<T>]) -> Future<[T]> {...}

Question is, what to do if input array is empty? Fail, assert? No, same rules apply, resulting future will instantly give you an empty array. Of course, in some domains that isn’t correct, but that should be explicitly stated on caller side.

2 Likes

Why not? Our CI server is idle overnight when no one is working and there are no commits being submitted for building. Is that unhealthy? Should our oncall get paged to respond? Of course not.

If emptiness of a job queue is a problem in your domain, that's a property of the queue, not its jobs, and it would be a completely misplaced responsibility to shoe-horn that into the allSatisfy call.

7 Likes