[Haskell-beginners] Here's why functions should return functions

Costello, Roger L. costello at mitre.org
Sun Jul 29 14:15:02 CEST 2012

Hi Folks,

Recently I had a small epiphany: when creating functions, design them to return functions rather than non-function values (Integer, Bool, list, tuple, etc.).

Here's why:

Consider a function that returns, say, the integer four ( 4 ). The type of the value returned by the function is this:

4 :: Num a => a

That is, the value returned is not a function, it is a number.

However, there are advantages to returning a function rather than a number. 

Recall the composition operator ( . )

Its operands are functions, e.g.

(+1) . (*3)

In programming, one school of thought is that programs should be written as a chain of function compositions:

a . b . c . d . e . f 

Composition is intimately connected to a branch of mathematics called Category Theory: 

      Category theory is based on composition as a fundamental 
      operation in much the same way that classical set theory is 
      based on the 'element of' or membership relation. ["Category 
      Theory for Computing Science" by Michael Barr and Charles 

By designing programs in this fashion--as a chain of compositions--you have Category Theory's vast body of knowledge to help you and give rigor to your programs.

Let's revisit the function mentioned above, the one that returns the integer four ( 4 ). If the function were to return the four cloaked in a function then that returned value could be used in a composition. That would very useful.

Here's a data type that lifts non-function values to functions:

data Lift a = Function a
                    deriving (Show)

The constructor ( Function ) is a function, as its type signature shows:

Function :: a -> Lift a

So rather than returning 4, return Function 4. Here's a function that converts any value to a function:

lift :: a -> Lift a
lift = Function

Thus, lift 4 returns Function 4

Given the Lift data type and the lift function we can now start chaining functions together. Here the value four is lifted and then composed with a successor function:

(successor . lift) 4           returns Function 5

where successor is defined as:

successor :: Num a => Lift a -> Lift a
successor (Function a) = lift (a + 1)

Notice that successor returns a function, not a non-function value. Consequently, the result of successor can also be used in a composition.

For example, here the value four is lifted, composed with successor, and then square is applied:

(square . successor . lift)  4           returns Function 25

where square is defined as:

square :: Num a => Lift a -> Lift a
square (Function a) = lift (a * a)

Once again notice that square also returns a function, not a non-function value. Consequently, the result of square can be used in a composition.

At some point we are finished manipulating the value four and want to just see the result, not the result wrapped in a constructor. So we can create a function to return the non-function value:

value :: Lift a -> a
value (Function a) = a

Here's an example:

(value . square . successor . lift) 4           returns 25

Comments welcome.


More information about the Beginners mailing list