As demonstrated by pointfree , any such conversion can be done automatically. However, the result is more often confused than improved. If one goal is to increase readability, and not to destroy it, then the first goal should be to determine why the expression has a certain structure, find a suitable abstraction and build things that way.
The simplest structure is simply combining things into a linear conveyor, which is a simple function. This gives us pretty far only in itself, but, as you noticed, it does not process everything.
One generalization is functions with additional arguments, which can be increased gradually. Here is one example: Define onResult = (. (.)) . Now, applying onResult n times to the initial id value, you get the composition of the function with the result of the n-ary function. Thus, we can define comp2 = onResult (.) And then write comp2 not (&&) to define the NAND operation.
Another generalization that covers the foregoing is indeed the definition of operators that apply a function to a component of a larger value. An example here would be first and second in Control.Arrow , which work with 2 tuples. Conal Elliott Semantic Editor Combinators are based on this approach.
The case is slightly different when you have a function with several arguments for some type b and a -> b function, and they need to be combined into a function with several arguments using a . For the general case of 2-ary functions, the Data.Function module provides an on combinator, which you can use to write expressions like compare `on` fst to compare two tuples in your first elements.
This is a more complex problem when one argument is used more than once, but there are significant duplicate patterns that can also be extracted. A special case here is the application of several functions to one argument, and then collecting the results with another function. This happens with the Applicative instance for functions, which allows us to write expressions like (&&) <$> (> 3) <*> (< 9) to check if the number falls into the given range.
It is important if you want to use any of this in real code, you need to think about what the expression means and how it is reflected in the structure. If you do this and then reorganize it into a dissolute style using meaningful combinators, you will often make the code more understandable than otherwise, unlike typical pointfree output.