May 21, 2011

Two Techniques in Functional Programming in Mathematica

When I code for customers, I often end up using techniques of functional programming that  many people haven't seen before. This often ends up being problematic, and I've begun to identify the techniques I use that cause the most confusion and explain them when needed. Nevertheless, I use these in my own code because they help in creating well organized, readable code.

There are two techniques in particular that I find the most useful: decorators and closures.

Decorators
I use the term decorator very broadly. For this case, I define a decorator to be a function which takes in a function or a result and instead of using it as input for some other process causes some kind of side effect (it's not a very good definition, but will work here). In Python, decorators are used when defining a function, but I often use the term to apply to higher order functions I wrap around other functions which have some generic usage.  A basic example is a "deprecated" decorator. To test if a function is a decorator, remove it from your code. It shouldn't really affect the core computations of your program. Decorators aren't really aren't integral to a  program. They simply decorate. A decorator should also be modular -- it shouldn't  be built to work with a specific function, but should be able to be used generally across many different functions.

This example of a decorator is used to mark functions as having been deprecated.

deprecated[function_] :=
Function[args,
Module[{}, Print["This function has been deprecated"];
function[args]]
]
We can then simply use this when defining functions to give them a deprecation warning.
test = deprecated@
Function[x, x + 1];
When test is ran now, it will warn that it is deprecated. The decorator could be used on pretty much any function definition. More advanced versions are possible. This version allows us to customize the deprecation message:
deprecated[replacement_?StringQ] :=
Function[function, Function[args,
Module[{},
Print["This function has been replaced by "<> replacement];
function[args]]
]]

Which can be used like:
test = deprecated["blarg"]@
Function[x, x + 1];

test[1]
This function has been replaced by blarg
Closures
Closures should be recognizable to anyone who knows functional programming. They provide a nice way to have state in a language without really explicitly mentioning state. Here is a really simple example in Mathematica:
makeCounter[] := Module[{count = 0}, Function[{}, count++]];
counter = makeCounter[];
counter[]
1
counter[]
2
....
Counter is a function which has a state because it references a variable in its parent function makeCounter.  This is cleaner than creating a global variable to hold the global count. The value of counter can only be properly accessed by using counter as we have intended it. I can't give a full treatment of closures and their uses here, but I hope this gives a good idea of what they are.

Combining them
I've used the both closures and decorators together with great synergy in a number of tasks. For example, let's say we want a good way to keeping track of how many times certain functions have been called. We can make a closure which returns a pair: a function to be used as a decorator on functions which are to increment the counter and a function to access the value of the counter. For example:
makeCounterSystem[] := Module[{count = 0}
{Function[result, count++; result],Function[{}, count]}];

{counts, totalCount} = makeCounterSystem[];
counts@ Sin[RandomReal[]] ;
totalCount[]
1
This decorator here is different from the previous ones in that it decorates functions not where they are defined, but where they are ran. This can be modified to be a decorator on the definition of the function easily if needed. Whenever we call a function which has counts@ preppended to it, the count will incremented and can then be access by calling totalCount[].

There are numerous uses for this kind of combination. A common use is to keep a log of the results of a function silently:
makeLoggingSystem[] := Module[{log = {}},
{Function[function, Function[args,
Module[{output=function[args]},
AppendTo[log,output]; output]]],
Function[{}, log]
}
];

{logger, getLog} = makeLoggingSystem[];
test = logger@
Function[x, N@Sin[x^2]];
Now whenever we call test, the resulting value is secretly logged and the entire history of the output of the function and any other function decorated by logger is accessible by running getLog[].