Lazy predicates

Lazy predicates are a more advanced feature that allow composing predicates that reference themself, or even having predicates with mutual references.

There are 3 types of lazy predicates:

  1. lazy_p: references another predicate by name.

  2. this_p: refers to the predicate to which the this_p belongs

  3. root_p: refers to the root predicate, i.e. the fully composed predicate

In the next chapters we will explain them in more detail

lazy_p

A lazy predicate is defined as:

from predicate import lazy_p

p = lazy_p("name_of_referenced_predicate")

To make this more clear, we will give to examples.

Example 1

In the next example we define a predicate, that tests if a given data structure is either a string, or a list of data that can again either be a string or a list of data. Ad infinitum.

from predicate import all_p, is_list_p, is_str_p, lazy_p

str_or_list_of_str = is_str_p | (is_list_p & all_p(lazy_p("str_or_list_of_str")))

Note that the name of the reference must be the same as the variable name of the predicate that you are referencing.

Applying this predicate gives the following results:

str_or_list_of_str("foo")
True

str_or_list_of_str(["foo"])
True

str_or_list_of_str(["foo", ["bar"]])
True

str_or_list_of_str(1)
False

Example 2

We can even model a predicate that checks if a given data structure is valid json:

valid_json_p = lazy_p("is_json_p")
json_list_p = is_list_p & lazy_p("valid_values")

json_keys_p = all_p(is_str_p)

valid_values = all_p(
    is_str_p | is_int_p | is_float_p | json_list_p | valid_json_p | is_none_p
)
json_values_p = comp_p(lambda x: x.values(), valid_values)

is_json_p = (is_dict_p & json_keys_p & json_values_p) | json_list_p

As you can see in this example there are 2 mutual lazy references.

this_p

A this predicate (this_p) is defined as:

from predicate import this_p

p = this_p

Note that this example is not very useful, since it references itself, leading to an infinite recursion while trying to evaluate.

For a more realist example, lets write example 1 from lazy_p using the this_p:

Example 1

from predicate import is_list_of_p, is_str_p, this_p

str_or_list_of_str = is_str_p | is_list_of_p(this_p)

We made two changes to the original lazy_p example. Firstly, are using the is_list_of_p predicate that combines the is_list and all_p predicates. But most importantly, instead of having to reference the str_or_list_of_str by name, we just use this_p, leading to more concise code.

root_p

A this predicate (root_p) is defined as:

from predicate import root_p

p = root_p

Note that this example is not very useful, since it references itself, leading to an infinite recursion while trying to evaluate.

For a more realist example, lets write example 1 from lazy_p using the root_p:

Example 1

from predicate import is_list_of_p, is_str_p, root_p

str_or_list_of_str = is_str_p | is_list_of_p(root_p)

We made two changes to the original lazy_p example. Firstly, are using the is_list_of_p predicate that combines the is_list and all_p predicates. But most importantly, instead of having to reference the str_or_list_of_str by name, we just use root_p, leading to more concise code.

Also note that in this particular case there is not difference between the this_p and the root_p