Functors and applicative functors

The idea of a functor is a functional representation of a piece of simple data. A functor version of the number 3.14 is a function of zero arguments that returns this value. Consider the following example:

>>> pi = lambda: 3.14
>>> pi()
3.14

We created a zero-argument lambda object that returns a simple value.

When we apply a curried function to a functor, we're creating a new curried functor. This generalizes the idea of applying a function to an argument to get a value by using functions to represent the arguments, the values, and the functions themselves.

Once everything in our program is a function, then all processing is simply a variation of the theme of functional composition. The arguments and results of curried functions can be functors. At some point, we'll apply a getValue() method to a functor object to get a Python-friendly, simple type that we can use in uncurried code.

Since the programming is based on functional composition, no calculation needs to be done until we actually demand a value using the getValue() method. Instead of performing a lot of intermediate calculations, our program defines intermediate complex objects that can produce a value when requested. In principle, this composition can be optimized by a clever compiler or runtime system.

When we apply a function to a functor object, we're going to use a method similar to map() that is implemented as the * operator. We can think of the function * functor or map(function, functor) methods as a way to understand the role a functor plays in an expression.

In order to work politely with functions that have multiple arguments, we'll use the & operator to build composite functors. We'll often see the functor & functor method used to build a functor object from a pair of functors.

We can wrap Python simple types with a subclass of the Maybe functor. The Maybe functor is interesting, because it gives us a way to deal gracefully with missing data. The approach we used in Chapter 11, Decorator Design Techniques, was to decorate built-in functions to make them None aware. The approach taken by the PyMonad library is to decorate the data so that it gracefully declines being operated on.

There are two subclasses of the Maybe functor:

  • Nothing
  • Just(some simple value)

We use Nothing as a stand-in for the simple Python value of None. This is how we represent missing data. We use Just(some simple value) to wrap all other Python objects. These functors are function-like representations of constant values.

We can use a curried function with these Maybe objects to tolerate missing data gracefully. Here's a short example:

>>> x1 = systolic_bp * Just(25) & Just(50) & Just(1) & Just(0)
>>> x1.getValue()
116.09

>>> x2 = systolic_bp * Just(25) & Just(50) & Just(1) & Nothing >>> x2.getValue() is None True

The * operator is functional composition: we're composing the systolic_bp() function with an argument composite. The & operator builds a composite functor
that can be passed as an argument to a curried function of multiple arguments.

This shows us that we get an answer instead of a TypeError exception. This can be very handy when working with large, complex datasets in which data could be missing or invalid. It's much nicer than having to decorate all of our functions to make them None aware.

This works nicely for curried functions. We can't operate on the Maybe functors in uncurried Python code as functors have very few methods.

We must use the getValue() method to extract the simple Python value for uncurried Python code.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset