pythonfunctional-programmingcomposition

Python's predicate composition


I would like to implement something similar to this OCaml in Python:

let example  = fun v opt_n ->
    let fltr = fun i -> i mod 2 = 0
    let fltr = match opt_n with 
                | None -> fltr
                | Some n -> fun i -> i mod n = 0 && fltr(n)
    fltr v

This is easily composable/extendable, I can add as many predicates as I want at runtime. This is of course a simplified example, in real life I have many optional inclusion/exclusion sets, and predicate checks for membership.

Doing this the naive way in Python fails:

def example(v: int, opt_n=None):
    """
    doesn't work!
    """
    # doesn't need to be a lambda, an explicitely defined function fails too
    fltr = lambda i: i % 2 == 0
    if opt_n is not None:
        # fails miserably -> maximum recursion depth exceeded
        fltr = lambda i: fltr(i) and i % opt_n == 0
    return fltr(v)

example(10, 5) 

This is annoying because it seems that since fltr can only appear once on the left side of the assignment, I have to inline the initial fltr in every case afterward:

def example(v: int, opt_n=None, opt_m=None):
    """annoying but works"""
    fltr = None
    # some inital filters
    pred_0 = lambda _: True # do some real checks ...
    pred_1 = lambda _: True  # do some real checks ...
    if opt_n is not None:
        # fltr is inlined, only appears on left side,  now it works
        fltr = lambda i: pred_0(i) and pred_1(i) and opt_n % 2 == 0
    if opt_m is not None:
        # much repetition
        fltr = lambda i: pred_0(i) and pred_1(i) and opt_n % 3 == 0
    if fltr is None:
        # inlined again
        fltr = lambda i: pred_0(i) and pred_1(i)
    return fltr(v)

Is there any way to fix my mess, maybe I am missing something, and/or what is the recommended way to compose predicates in Python?


Solution

  • When you write

    fltr = lambda i: fltr(i) and i % opt_n == 0
    

    fltr remains a free variable inside the lambda expression, and will be looked up when the function is called; it's not bound to the old definition of fltr in place when you evaluate the lambda expression.

    You need some way to do early binding; one option is to bind the current value of fltr to a new variable that's local to the lambda expression, namely another parameter:

    fltr = lambda i, cf=fltr: cf(i) and i % opt_n == 0
    

    Not the cleanest solution. If you don't mind an additional function call, you can define an explicit composition function:

    def pred_and(f, g):
        return lambda x: f(x) and g(x)
    

    then use that to compose the old fltr with another predicate to produce a new filter.

    def example(v: int, opt_n=None):
        fltr = lambda i: i % 2 == 0
        if opt_n is not None:
            # fails miserably -> maximum recursion depth exceeded
            fltr = pred_and(fltr, lambda i: i % opt_n == 0)
        return fltr(v))
    

    (I don't know OCaml very well, but pred_and is somewhat like the use of an applicative functor in Haskell, e.g. pred_and = liftA2 (&&).)