Towards point-free redux

Over the last two posts I’ve made a few efforts to explain how we can drop of explicit argument references when moving functions towards point-free style. We’ve looked at currying, partial function application, and function composition as explanations for this. These concepts are really important, but after kicking around various ideas about this with a colleague I’ve decided to try and separate all these concepts from the most fundamental form of reducing a function towards point-free.

Eta reduction

Say we have two functions, f and g, with exactly the same types. Both take one argument of type a and return something of type b. We’ll define f in terms of g:

f :: a -> b
g :: a -> b

f = \a -> g a

In Haskell this this can also be written as:

f a = g a       ... (1)

If f and g are pure functions (deterministic, no side-effects, no state), then we have:

f = g           ... (2)

We’ve just said in (1) that the functions are equal when applied with the same argument, so for all values in type a the functions f and g will be equivalent; we can use them interchangeably. We don’t have to worry about partial application, or currying, or anything else; we can just apply this equivalency.

Removing redundant argument references like this is called eta reduction (also written as η-reduction). Continually reducing in this way can lead to point-free functions once we’ve omitted all redundant argument references.

Applying with multiple arguments

We can apply this same equivalency to our previous example with multiple arguments:

z = \acc x -> const (acc+1) x

-- `z` is of the form:  
--      f x = g x 
-- where:
--      f = z acc
--      g = const (acc+1)

z = \acc -> const (acc+1)

We use currying, partial application, and concepts like function composition to get our functions into the required f x = g x form, but once we’re there we can use η-reduction to drop the explicit reference to the last argument.

Impure functions

Notice that this doesn’t apply for impure functions, as we could have:

int f(int x) { Console.WriteLine(x); return g(x); }
int g(int x) { return x+1; }

In this case we can’t use f and g interchangeably as they behave differently. In a pure functional language like Haskell we don’t have to worry about this; the type system will differentiate functions with side-effects.