Partial application is when you take a function which takesnarguments and you supply it with< n of them. When discussing sections in Section 7.1, we saw a form of “partial application” in which functions like+were partially applied. For instance, in the expressionmap (+1) [1,2,3], the section(+1)is a partial application of+. This is because+really takes two arguments, but we’ve only given it one.
Partial application is very common in function definitions and sometimes goes by the name “eta reduction”. For instance, suppose we are writting a functionlcaseString
eta reduction
which converts a whole string into lower case. We could write this as:
lcaseString s = map toLower s
Here, there is no partial application (though you could argue that applying no argu- ments totoLowercould be considered partial application). However, we notice that the application ofsoccurs at the end of bothlcaseStringand ofmap toLower. In fact, we can remove it by performing eta reduction, to get:
lcaseString = map toLower
Now, we have a partial application of map: it expects a function and a list, but we’ve only given it the function.
This all is related to type type of map, which is(a→b)→([a]→[b]), when parentheses are all included. In our case,toLoweris of typeChar→Char. Thus, if we supply this function tomap, we get a function of type[Char]→[Char], as desired. Now, consider the task of converting a string to lowercase and remove all non letter characters. We might write this as:
7.3. PARTIAL APPLICATION 77
lcaseLetters s = map toLower (filter isAlpha s)
But note that we can actually write this in terms of function composition:
lcaseLetters s = (map toLower . filter isAlpha) s
And again, we’re left with an eta reducible function:
lcaseLetters = map toLower . filter isAlpha
Writing functions in this style is very common among advanced Haskell users. In
fact it has a name: point-free programming (not to be confused with pointless program- point-free programming
ming). It is call point free because in the original definition oflcaseLetters, we can think of the valuesas a point on which the function is operating. By removing the point from the function definition, we have a point-free function.
A function similar to(.)is($). Whereas(.) is function composition,($)is $
function application. The definition of($)from the Prelude is very simple: function application
f $ x = f x
However, this function is given very low fixity, which means that it can be used to replace parentheses. For instance, we might write a function:
foo x y = bar y (baz (fluff (ork x)))
However, using the function application function, we can rewrite this as:
foo x y = bar y $ baz $ fluff $ ork x
This moderately resembles the function composition syntax. The($)function is also useful when combined with other infix functions. For instance, we cannot write:
Prelude> putStrLn "5+3=" ++ show (5+3)
because this is interpreted as(putStrLn "5+3=") ++ (show (5+3)), which makes no sense. However, we can fix this by writing instead:
Prelude> putStrLn $ "5+3=" ++ show (5+3)
Which works fine.
Consider now the task of extracting from a list of tuples all the ones whose first component is greater than zero. One way to write this would be:
fstGt0 l = filter (\ (a,b) -> a>0) l
We can first apply eta reduction to the whole function, yielding:
fstGt0 = filter (\ (a,b) -> a>0)
Now, we can rewrite the lambda function to use the fstfunction instead of the pattern matching:
fstGt0 = filter (\x -> fst x > 0)
Now, we can use function composition betweenfstand>to get:
fstGt0 = filter (\x -> ((>0) . fst) x)
And finally we can eta reduce:
fstGt0 = filter ((>0).fst)
This definition is simultaneously shorter and easier to understand than the original. We can clearly see exactly what it is doing: we’re filtering a list by checking whether something is greater than zero. What are we checking? Thefstelement.
While converting to point free style often results in clearer code, this is of course not always the case. For instance, converting the following map to point free style yields something nearly uninterpretable:
foo = map (\x -> sqrt (3+4*(xˆ2))) foo = map (sqrt . (3+) . (4*) . (ˆ2))
There are a handful of combinators defined in the Prelude which are useful for point free programming:
• uncurrytakes a function of typea→b→cand converts it into a function of type(a,b)→c. This is useful, for example, when mapping across a list of pairs:
Prelude> map (uncurry (*)) [(1,2),(3,4),(5,6)] [2,12,30]
• curryis the opposite ofuncurryand takes a function of type(a,b)→cand produces a function of typea→b→c.
• flipreverse the order of arguments to a function. That is, it takes a function of typea→b→cand produces a function of typeb→a→c. For instance, we can sort a list in reverse order by usingflip compare:
7.3. PARTIAL APPLICATION 79
Prelude> List.sortBy compare [5,1,8,3] [1,3,5,8]
Prelude> List.sortBy (flip compare) [5,1,8,3] [8,5,3,1]
This is the same as saying:
Prelude> List.sortBy (\a b -> compare b a) [5,1,8,3] [8,5,3,1]
only shorter.
Of course, not all functions can be written in point free style. For instance:
square x = x*x
Cannot be written in point free style, without some other combinators. For instance, if we can define other functions, we can write:
pair x = (x,x)
square = uncurry (*) . pair
But in this case, this is not terribly useful.
Exercises
Exercise 7.1 Convert the following functions into point-free style, if possible.
func1 x l = map (\y -> y*x) l
func2 f g l = filter f (map g l)
func3 f l = l ++ map f l
func4 l = map (\y -> y+2)
(filter (\z -> z ‘elem‘ [1..10]) (5:l))