Search code examples
functionhaskellpointfree

What are the prerequisites for a point-free function in Haskell


I always thought that the prerequisites for a pointfree function were to get the function arguments to the end of the definition. E.g.

-- This can be made pointfree quite easily:    
let lengths x = map length x    
let lengths' = map length

-- However this cannot:
let lengthz x = length `map` x
-- let lengthz' = length `map` (parse error)

I originally came across this reading this question. There we have this example:

agreeLen :: (Eq a) => [a] -> [a] -> Int
agreeLen x y = length $ takeWhile id $ zipWith (==) x y
-- This may look like it can easily be made pointfree, however it cannot
-- agreeLen' :: (Eq a) => [a] -> [a] -> Int
-- agreeLen' = length $ takeWhile id $ zipWith (==) (zipWith is applied to too few arguments)

So why can my first example be made pointfree, but the other two cannot?


Solution

  • -- However this cannot:
    let lengthz x = length `map` x
    -- let lengthz' = length `map` (parse error)
    

    \x -> length `map` x written point free is simply map length. The infix backticks are just syntactical sugar. (As chepner points out, if you really want it you can use a section, i.e. (length `map`).)

    agreeLen :: (Eq a) => [a] -> [a] -> Int
    agreeLen x y = length $ takeWhile id $ zipWith (==) x y
    -- This may look like it can easily be made pointfree, however it cannot
    

    The key word here is "easily". Pretty much anything can be made point-free if you try hard enough. In this case, omitting the y parameter is easy if we write agreeLen in terms of (.) rather than ($):

    agreeLen x y = (length . takeWhile id . zipWith (==) x) y
    agreeLen x = length . takeWhile id . zipWith (==) x
    

    As for x, we can handle it by treating the use of (.) to compose zipWith (==) x with the other functions as just another case of a value being modified with a function:

    agreeLen x = (.) (length . takeWhile id) (zipWith (==) x)
    agreeLen x = ((length . takeWhile id) .) (zipWith (==) x) -- cosmetical change
    agreeLen x = (((length . takeWhile id) .) . zipWith (==)) x
    agreeLen = ((length . takeWhile id) .) . zipWith (==)
    

    It is not something you'd actually want to do in practice, but it is certainly possible.