Higher-order Functions
The call() method is how the resulting function is evaluated. In this
case, the function that was created will gracefully tolerate None values without
raising exceptions.
The common approach is to create the new function and save it for future use
by assigning it a name as follows:
null_log_scale= NullAware(math.log)
This creates a new function and assigns the name null_log_scale(). We can then
use the function in another context. Take a look at the following example:
some_data = [10, 100, None, 50, 60]
scaled = map(null_log_scale, some_data)
list(scaled)
[2.302585092994046, 4.605170185988092, None, 3.912023005428146,
4.0943445622221]
A less common approach is to create and use the emitted function in one expression
as follows:
scaled= map(NullAware( math.log ), some_data)
list(scaled)
[2.302585092994046, 4.605170185988092, None, 3.912023005428146,
4.0943445622221]
The evaluation of NullAware( math.log ) created a function. This anonymous
function was then used by the map() function to process an iterable, some_data.
This example's call() method relies entirely on expression evaluation. It's
an elegant and tidy way to define composite functions built up from lower-level
component functions. When working with scalar functions, there are a few complex
design considerations. When we work with iterable collections, we have to be a bit
more careful.
Assuring good functional design
The idea of stateless functional programming requires some care when using Python
objects. Objects are typically stateful. Indeed, one can argue that the entire purpose
of object-oriented programming is to encapsulate state change into class definition.
Because of this, we find ourselves pulled in opposing directions between functional
programming and imperative programming when using Python class definitions to
process collections.