[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
Wells]
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.
/Roger
More information about the Beginners
mailing list