I have seen a lot of functions being defined according to the pattern (f .) . g
. For example:
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
What does this mean?
I have seen a lot of functions being defined according to the pattern (f .) . g
. For example:
countWhere = (length .) . filter
duplicate = (concat .) . replicate
concatMap = (concat .) . map
What does this mean?
The dot operator (i.e.
(.)
) is the function composition operator. It is defined as follows:As you can see it takes a function of type
b -> c
and another function of typea -> b
and returns a function of typea -> c
(i.e. which applies the result of the second function to the first function).The function composition operator is very useful. It allows you to pipe the output of one function into the input of another function. For example you could write a tac program in Haskell as follows:
Not very readable. Using function composition however you could write it as follows:
As you can see function composition is very useful but you can't use it everywhere. For example you can't pipe the output of
filter
intolength
using function composition:The reason this is not allowed is because
filter
is of type(a -> Bool) -> [a] -> [a]
. Comparing it witha -> b
we find thata
is of type(a -> Bool)
andb
is of type[a] -> [a]
. This results in a type mismatch because Haskell expectslength
to be of typeb -> c
(i.e.([a] -> [a]) -> c
). However it's actually of type[a] -> Int
.The solution is pretty simple:
However some people don't like that extra dangling
f
. They prefer to writecountWhere
in pointfree style as follows:How do they get this? Consider:
As you can see
(f .) . g
is simply\x y -> f (g x y)
. This concept can actually be iterated:It's not pretty but it gets the job done. Given two functions you can also write your own function composition operators:
Using the
(.:)
operator you could writecountWhere
as follows instead:Interestingly though you could write
(.:)
in point free style as well:Similarly we get:
As you can see
(.:)
,(.::)
and(.:::)
are just powers of(.)
(i.e. they are iterated functions of(.)
). For numbers in Mathematics:Similarly for functions in Mathematics:
If
f
is(.)
then:That brings us close to the end of this article. For a final challenge let's write the following function in pointfree style:
We can further simplify this as follows:
Using
compose
you can now writemf
as:Yes it is a bit ugly but it's also a really awesome mind-boggling concept. You can now write any function of the form
\x y z -> f x (g y z)
ascompose f g
and that is very neat.This is a matter of taste, but I find such style to be unpleasant. First I'll describe what it means, and then I suggest an alternative that I prefer.
You need to know that
(f . g) x = f (g x)
and(f ?) x = f ? x
for any operator?
. From this we can deduce thatso
I prefer to use a function called
.:
Then
countWhere = length .: filter
. Personally I find this a lot clearer.(
.:
is defined inData.Composition
and probably other places too.)