What (f.). Does g mean in Haskell?

I have seen many functions defined according to the pattern (f .) . g (f .) . g . For example:

 countWhere = (length .) . filter duplicate = (concat .) . replicate concatMap = (concat .) . map 

What does it mean?

+48
functional-programming haskell function-composition pointfree tacit-programming
Nov 29 '13 at 5:57
source share
2 answers

The point operator (i.e. (.) ) Is the function composition operator. It is defined as follows:

 infixr 9 . (.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (gx) 

As you can see, it takes a function of type b → c and another function of type a → b and returns a function of type a → c (i.e. which applies the first function to the result of the second function).

The function composition operator is very useful. This allows you to direct the output of one function to the input of another function. For example, you can write a Tac program in Haskell as follows:

 main = interact (\x -> unlines (reverse (lines x))) 

Not very readable. Using function composition, you can write it like this:

 main = interact (unlines . reverse . lines) 

As you can see, compiling functions is very useful, but you cannot use it everywhere. For example, you cannot redirect filter output to length using the composition of the function:

 countWhere = length . filter -- this is not allowed 

The reason this is unacceptable is that filter is of type (a → Bool) → [a] → [a] . Comparing it with a → b we find that a is of type (a → Bool) and b is of type [a] → [a] . This leads to type mismatch, because Haskell expects length to be of type b → c (that is, ([a] → [a]) → c ). However, it is actually of the type [a] → Int .

The solution is quite simple:

 countWhere f = length . filter f 

However, some people do not like this excess chatter f . They prefer to write countWhere in pointfree style like this:

 countWhere = (length .) . filter 

How do they get it? To consider:

 countWhere f xs = length (filter f xs) -- But 'fxy' is '(fx) y'. Hence: countWhere f xs = length ((filter f) xs) -- But '\x -> f (gx)' is 'f . g'. Hence: countWhere f = length . (filter f) -- But 'f . g' is '(f .) g'. Hence: countWhere f = (length .) (filter f) -- But '\x -> f (gx)' is 'f . g'. Hence: countWhere = (length .) . filter 

As you can see (f.). g (f.). g (f.). g (f.). g is just \xy → f (gxy) . This concept can indeed be repeated:

 f . g --> \x -> f (gx) (f .) . g --> \xy -> f (gxy) ((f .) .) . g --> \xyz -> f (gxyz) (((f .) .) .) . g --> \wxyz -> f (gwxyz) 

It is not beautiful, but it does the job. With two functions, you can also write your own function composition operators:

 f .: g = (f .) . g f .:: g = ((f .) .) . g f .::: g = (((f .) .) .) . g 

Using the operator (.:) you can write countWhere as follows:

 countWhere = length .: filter 

Interestingly, you could write (.:) in the free point style:

 f .: g = (f .) . g -- But 'f . g' is '(.) f g'. Hence: f .: g = (.) (f .) g -- But '\x -> fx' is 'f'. Hence: (f .:) = (.) (f .) -- But '(f .)' is '((.) f)'. Hence: (f .:) = (.) ((.) f) -- But '\x -> f (gx)' is 'f . g'. Hence: (.:) = (.) . (.) 

Similarly, we get:

 (.::) = (.) . (.) . (.) (.:::) = (.) . (.) . (.) . (.) 

As you can see (.:) , (.::) and (.:) are simply degrees (.) (I.e. they are iterative functions (.) ). For numbers in math:

 x ^ 0 = 1 x ^ n = x * x ^ (n - 1) 

Similarly for functions in math:

 f .^ 0 = id f .^ n = f . (f .^ (n - 1)) 

If f (.) Then:

 (.) .^ 1 = (.) (.) .^ 2 = (.:) (.) .^ 3 = (.::) (.) .^ 4 = (.:::) 

This brings us closer to the end of this article. For the final test we will write the following function in the style of pointfree:

 mf abc = filter a (map bc) mf abc = filter a ((map b) c) mf ab = filter a . (map b) mf ab = (filter a .) (map b) mf a = (filter a .) . map mf a = (. map) (filter a .) mf a = (. map) ((filter a) .) mf a = (. map) ((.) (filter a)) mf a = ((. map) . (.)) (filter a) mf = ((. map) . (.)) . filter mf = (. map) . (.) . filter 

We can further simplify this as follows:

 compose fg = (. f) . (.) . g compose fg = ((. f) . (.)) . g compose fg = (.) ((. f) . (.)) g compose f = (.) ((. f) . (.)) compose f = (.) ((. (.)) (. f)) compose f = ((.) . (. (.))) (. f) compose f = ((.) . (. (.))) (flip (.) f) compose f = ((.) . (. (.))) ((flip (.)) f) compose = ((.) . (. (.))) . (flip (.)) 

Using compose you can write mf as:

 mf = compose map filter 

Yes, it's a little ugly, but it's also a really awesome stunning concept. Now you can write any function of the form \xyz → fx (gyz) as compose fg and this is very convenient.

+89
Nov 29 '13 at 5:57
source share
— -

This is a matter of taste, but I find this style unpleasant. First, I will describe what this means, and then I suggest the alternative that I prefer.

Do you need to know that (f . g) x = f (gx) and (f ?) x = f ? x (f ?) x = f ? x for any operator ? . From this we can conclude that

 countWhere p = ((length .) . filter) p = (length .) (filter p) = length . filter p 

So

 countWhere p xs = length (filter p xs) 

I prefer to use the function .:

 (.:) :: (r -> z) -> (a -> b -> r) -> a -> b -> z (f .: g) xy = f (gxy) 

Then countWhere = length .: filter . Personally, I find it much clearer.

( .: defined in Data.Composition and possibly elsewhere.)

+11
Nov 29 '13 at 8:24
source share



All Articles