Dugan Chen's Homepage

Various Things

A Point Free Function Composition Operator For Python

Here’s a quick implementation of a point free function composition operator for Python:

compose = lambda f, g: lambda *args, **kwargs:\
        f(g(*args, **kwargs))

class composable(object):

    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):

        return self.f(*args, **kwargs)

    def __mul__(self, other):
        return composable(compose(self, other))

The composable class works as a function decorator:

@composable
def append_a(string):
    return '{0}{1}'.format(string, 'a')


@composable
def append_b(string):
    return '{0}{1}'.format(string, 'b')


@composable
def append_c(string):
    return '{0}{1}'.format(string, 'c')

The following test prints out “dcba”, which is what you would expect if you start with “d”, append “c” to it, append “b” to it, and then append “a” to it:

f = append_a * append_b * append_c

print f('d')

One use case for this is that to very elegantly and progressively add attributes to Django’s form widgets. Let’s say we want to add the HTML5, “required” attribute, and a “required” class to make sure jquery-validation works. We also want to add a “input-file” class to file fields, so that the Twitter Bootstrap renders them properly.

With the help of the add_class() function from my last entry, we make this work:


@composable
def has_attrs(field):
    field.widget.attrs = field.widget.attrs or None
    return field

@composable
is_required(field):
    if field.required != False:
        field.widget.attrs['required'] = 'required'
        add_class(field.widget.attrs, 'required')
    return field

@composable
is_file(field):
    add_class(field.widget.attrs, 'input-file')
    return field

def add_class(attrs, html_class):
    assert type(attrs) is dict

    if 'class' in attrs:
        classes = attrs['class'].split()
        if not html_class in classes:
            classes.append(html_class)
            attrs['class'] = ' '.join(classes)
    else:
        attrs['class'] = html_class

FileField = is_file * is_required * has_attrs * FileField
CharField = is_required * has_attrs * CharField