Combinators in Python
Inspired by the Coffeescript library Katy, I've been exploring using combinators with Python. The result can be found in my Functional Python repository.
In my previous post on Function Python, I was attempting to translate the following imperative code into different styles.
def imperative_style(xs):
results = []
for x in xs:
if x >= 7:
break
if x < 2:
result = 4 * x
results.append(result)
return results
assert imperative_style(range(10)) == [0, 4]
By using the linked Combinators
class, we can translate that to the following:
from itertools import takewhile
def fluent_combinator_style(xs):
return bw(xs).chain()\
.R(takewhile, lambda x: x < 7)\
.R(filter, lambda x: x < 2)\
.R(map, lambda x: 4 * x)\
.value()
assert fluent_combinator_style(range(10)) == [0, 4]
The combinator methods work by altering the form of function calls. The R
method transforms bw(wrapped_value).R(takewhile, lambda x: x < 7)
into the call takewhile(lambda x: x < 7, wrapped_value)
.
What I find interesting about this style is that it allows the end user of a class to add something that looks like a method to an object.
As I experimented using this class wrapping iterables, it began to remind me of Underscore.js. This resulted in a tiny (69 line) iterable wrapper It
. Using It
, we can redefine the above like so:
from itertools import takewhile
def it_style(xs):
return It(xs).chain()\
.R(takewhile, lambda x: x < 7)\
.filter(lambda x: x < 2)\
.map(lambda x: 4 * x)\
.value()
assert it_style(range(10)) == [0, 4]
Notice how It
doesn't support takewhile
out of the box, but the user can easily add it to the method chain. It provides flexibility on the level of the object instance, allowing the user to add functionality in a lightweight manner.