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?
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 (&&)
.)