1

Discovered this after discovering the all( ) function in Python doesn't shortcircuit as I expect because the expressions inside the list you send are not lazily evaluated. So:

all([
    previous_task is not None,
    self.task_type == previous_task.task_type,
    self.owner is None,
    previous_task.owner is None,
    self.is_sibling_of(previous_task)
    ])

In this code for example, if previous_task is None, self.task_type == previous_task.task_type will still evaluate and throw an Exception while trying to access previous_task.task_type

Although this is easily fixable like this (and actually shortcircuits AND is lazy):

previous_task is not None and all([ 
    self.task_type == previous_task.task_type,
    self.owner is None,
    previous_task.owner is None,
    self.is_sibling_of(previous_task)
    ])

The core problem actually lies in that the list expression will evaluate the code inside eagerly while creating it.

So the solution is to create a generator, so it actually only evaluates the item one by one. But... generators require a comprenhension from something to actually generate. Or a function with the yield argument.

And although I could do something like this:

def __evaluate(self, previous_task):
    yield previous_task is not None
    yield self.task_type == previous_task.task_type
    yield self.owner is None
    yield previous_task.owner is None
    yield self.is_sibling_of(previous_task)

Readability suffers, defeating the purpose of all.

And I found on the PEP that you can use something like this:

(condition 
    for evaluation 
    in (
        previous_task is not None,
        self.task_type == previous_task.task_type,
        self.owner is None,
        previous_task.owner is None,
        self.is_sibling_of(previous_task)
    )
)

That wont work either, because the second parentheses is a set literal, not a generator, and it will evaluate the contents of the set on construction just like the list.

Another option is to make a reduce argument, from a list of lambdas, like this:

reduce(
    lambda actual, next_function: actual and next_function(),
    [
        lambda: previous_task is not None
        lambda: self.task_type == previous_task.task_type
        lambda: self.owner is None
        lambda: previous_task.owner is None
        lambda: self.is_sibling_of(previous_task)
    ]
)

Is overly verbose, even though is still quite readable

So... is there something I'm missing? Is there any way to be able to do this in a concise way? It seems like overly hard for just lazy condition evaluation

If these are the only possibilities, I might consider doing an utils library D:

Edit: Forgot the obvious solution, which I didn't make explicit, where you could just chain ands, but the thing is that this defeats the purpose of all. And that I thought that knowing how to make generators of unrelated values is much useful in the long way than just spamming and which hinders readability and forces you to "hard-code" the conditions in an huge expression

    (previous_task is not None
and self.task_type == previous_task.task_type
and self.owner is None
and previous_task.owner is None
and self.is_sibling_of(previous_task)
    )
JoshiRaez
  • 97
  • 9
  • 1
    How about `all(cond() for cond in [lambda list])`? – tzaman Aug 20 '20 at 13:15
  • Hey, that's a really good one, and is akin to the reduce one I put. The thing is that the lambda list adds a LOT of boilerplate code, just like using reduce or yield But is the option I like the most of all the ones I have seen. – JoshiRaez Aug 20 '20 at 13:19
  • Nevermind, tried it out, is still overly verbose because you end adding 0324832987498324 `lambda` boilerplate Is there no way for python to do lazy without using lambda expression? – JoshiRaez Aug 20 '20 at 13:28
  • 1
    Not without putting it in a function. TBH in the example you've shown I'd just go with the chained `and` construction. `all` is there as a convenience, but if you're already writing out all the expressions I don't see how using `and` is any more or less "hard-coded" than any alternative. – tzaman Aug 20 '20 at 13:43
  • Specially because is more error prone (you might miss an `and` or something), and it makes harder to refactorize the code (you can always cut out the list and put it somewhere else, extend it however you want, append to it, you get it) But yeah, unless someone discovers for us some way we dont know, it seems like you will have to put preconditions in an `and` before using `all` :( – JoshiRaez Aug 20 '20 at 13:45

0 Answers0