Point free style with infix notation - haskell

Hello is there a way to write point free style when using infix notation?
f::Int->Int->Int->Int
f a b=(+) (a+b)
Why you cannot do something like this ?
f::Int->Int->Int->Int
f a b=(a+b) +
or
f a b= (a+b) `+`
Can you not combine operators in point free style like e.g?
ptfree::Int->Int->Int->Int
ptfree=(+) (+)
I mean you can chop arguments of functions like fold but why not for operator arguments?

Well since you need to pass two parameters, we can use what is known as the "surprised owl operator". This is basically a composition of parameters. So we can use:
f = ((.).(.)) (+) (+)
Or we can more inline the operator like:
f = ((+) .) . (+)
The owl operator ((.).(.)) f g basically is short for \x y -> f (g x y)
How does this work?
The canonical form of the "surprised owl operator" is:
= ((.) . (.))
------------- (canonical form)
(.) (.) (.)
So we can now replace the (.)s with corresponding lambda expressions:
(\f g x -> f (g x)) (.) (.)
So now we can perform some replacements:
(\f g x -> f (g x)) (.) (.)
-> (\x -> (.) ((.) x))
-> (\x -> (\q r y -> q (r y)) ((.) x))
-> (\x -> (\r y -> ((.) x) (r y)))
-> (\x r y -> ((.) x) (r y))
-> (\x r y -> ((\s t u -> s (t u)) x) (r y))
-> (\x r y -> (\t u -> x (t u)) (r y))
-> (\x r y -> (\u -> x ((r y) u)))
-> \x r y u -> x ((r y) u))
-> \x r y u -> x (r y u)
So basically it means that our surprised owl operator, is equal to:
surprised_owl :: (y -> z) -> (a -> b -> y) -> a -> b -> z
surprised_owl f g x y = f (g x y) -- renamed variables
And if we now specialize this with the fuctions provided (two times (+)), we get:
f = surprised_owl (+) (+)
so:
f x y = (+) ((+) x y)

You must compose (+) with (+) twice, for it to be completely point-free: f = ((+) .) . (+)
Recall that composition is defined as
(f . g) x = f (g x)
or, equivalently:
(f . g) = \x -> f (g x)
So, if you look at the composition f = ((+) .) . (+) and work backwards using the definition of (.):
f = ((+) .) . (+)
f = \x -> ((+) .) ((+) x) -- definition of (.)
f = \y -> (\x -> (+) (((+) x) y)) -- definition of (.)
f x y = (+) (((+) x) y) -- a simpler way to write this
f x y z = (+) (((+) x) y) z -- explicitly add in the final argument (eta expansion)
f x y z = ((+) x y) + z -- rewrite as infix
f x y z = (x + y) + z -- rewrite as infix
and you see we end up with what we started before we tried to make it point-free, so we know that this definition works. Going the other way through the steps above, roughly bottom-to-top, could give you an idea of how you might find such a point-free definition of a function like f.
When you "leave off" multiple arguments from the "end" like this, you usually must compose multiple times. Working through a few similar functions should help build intuition for this.
Note: I wouldn't generally recommend using this sort of point-free (when it complicates things) in production code.

Related

Eta reduction in haskell

I tried for a long time to reduct this function in haskell, I want to express for example:
mySum x y = x + y
mySum x y = (+) x y
mySum x = (+) x
mySum = (+) -- it's Messi's goal!
My function it a little more complex, but I really can't do it, I was looking out here and there, and I know there are some techniques, like modify the right side, and use flip. I tried and I got stuck here:
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f x y = map (uncurry f) (zip x y)
Steps:
zipWith' f x y = map (uncurry f) (zip x y)
zipWith' f x y = flip map (zip x y) (uncurry f)
zipWith' f x y = flip map (zip x y) $ uncurry f
and then I don't know how to continue...
I'm looking for an answer that could explain step by step how to achieve the "Messi's goal", I know is a lot to ask, so I will add as soon as I can a bounty to thank the effort
zipWith' f x y = map (uncurry f) (zip x y)
Rewrite application to composition and eta-reduce:
-- \y -> let g = map (uncurry f); h = zip x in (g . h) y
-- let g = map (uncurry f); h = zip x in g . h
zipWith' f x = map (uncurry f) . zip x
Rewrite infix to prefix:
-- g . h = (.) g h
zipWith' f x = (.) (map (uncurry f)) (zip x)
Rewrite application to composition and eta-reduce:
-- \x -> let g = (.) (map (uncurry f)); h = zip in (g . h) x
-- let g = (.) (map (uncurry f)); h = zip in g . h
zipWith' f = (.) (map (uncurry f)) . zip
Rewrite infix to prefix:
-- g . h = (.) g h
zipWith' f = (.) ((.) (map (uncurry f))) zip
Use flip to move f to the right-hand side:
-- flip f x y = f y x
zipWith' f = flip (.) zip ((.) (map (uncurry f)))
Rewrite application to composition:
-- g (h (i x)) = (g . h . i) x
zipWith' f = flip (.) zip (((.) . map . uncurry) f)
Rewrite application to composition and eta-reduce:
-- \f -> let g = flip (.) zip; h = (.) . map . uncurry in (g . h) f
-- let g = flip (.) zip; h = (.) . map . uncurry in g . h
zipWith' = (flip (.) zip) . ((.) . map . uncurry)
Remove redundant parentheses:
zipWith' = flip (.) zip . (.) . map . uncurry
And simplify to infix if you like:
zipWith' = (. zip) . (.) . map . uncurry
This result isn’t very readable, though.
Often when writing fully point-free code, you want to take advantage of the -> applicative and arrow combinators from Control.Arrow. Rather than trying to write a function like \ f x y -> ..., you can start by grouping the arguments into tuples to make them easier to rearrange and pipe around. In this case I’ll use \ (f, (x, y)) -> ...
\ (f, (x, y)) -> map (uncurry f) (zip x y)
We can eliminate the unpacking of (x, y) by applying uncurry to zip:
\ (f, (x, y)) -> map (uncurry f) (uncurry zip (x, y))
\ (f, xy) -> map (uncurry f) (uncurry zip xy)
Now we have a simple case: applying two functions (uncurry and uncurry zip) to two arguments (f and xy), then combining the results (with map). For this we can use the *** combinator from Control.Arrow, of type:
(***) :: Arrow a => a b c -> a b' c' -> a (b, b') (c, c')
Specialised to functions, that’s:
(***) #(->) :: (b -> c) -> (b' -> c') -> (b, b') -> (c, c')
This just lets us apply a function to each element of a pair. Perfect!
uncurry *** uncurry zip
:: (a -> b -> c, ([x], [y])) -> ((a, b) -> c, [(x, y)])
You can think of uncurry f as combining the elements of a pair using the function f. So here we can combine the results using uncurry map:
uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c, ([a], [b])) -> [c]
And you can think of curry as turning a function on tuples into a multi-argument function. Here we have two levels of tuples, the outer (f, xy) and the inner (x, y). We can unpack the outer one with curry:
curry $ uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c) -> ([a], [b]) -> [c]
Now, you can think of fmap f in the -> applicative as “skipping over” the first argument:
fmap #((->) _) :: (a -> b) -> (t -> a) -> t -> b
So we can unpack the second tuple using fmap curry:
fmap curry $ curry $ uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c) -> [a] -> [b] -> [c]
And we’re done! Or not quite. When writing point-free code, it pays to break things out into many small reusable functions with clearer names, for example:
zipWith' = untuple2 $ combineWith map apply zipped
where
untuple2 = fmap curry . curry
combineWith f g h = uncurry f . (g *** h)
apply = uncurry
zipped = uncurry zip
However, while knowing these techniques is useful, all this is just unproductive trickery that’s easy to get lost in. Most of the time, you should only use point-free style in Haskell when it’s a clear win for readability, and neither of these results is clearer than the simple original version:
zipWith' f x y = map (uncurry f) (zip x y)
Or a partially point-free version:
zipWith' f = map (uncurry f) .: zip
where (.:) = (.) . (.)

Haskell complex function composition

I'm trying to learn function composition in Haskell. I have following exercise.
I have function:h x y = f (g x y)and i need to find function composition equal to it:
a) f . g
b) (f.).g
c) (.)f(.g)
d) f(.g)
I know that (.)f g = f.g = f(g x) but i don't understand this complicated function composition
The correct answer is (b): (f.).g
Let us analyze this function. The (f.) part is short for ((.) f), so we already solved that one, and thus the function - without syntactical sugar - is:
(.) ((.) f) g
Now we can rewrite the first (.) function to a lambda-expression:
\x -> ((.) f) (g x)
And now when we evaluate the second function on the left (((.) f)) further, we obtain:
\x -> (.) f (g x)
or:
\x -> f . g x
So if we convert the (.) function in a lambda expression, we obtain:
\x -> (\y -> f ((g x) y))
Now we can make this expression more elegantly. (g x) y can be rewritten to g x y:
\x -> (\y -> f (g x y))
and we can rewrite nested lambda expressions into a single lambda expression:
\x y -> f (g x y)
Which is what we wanted.
You can also use (.) (.) (.) - despite it being somewhat challenging to understand, the evaluation results in an elegant form identical to the one you're looking for
comp2 = (.) (.) (.)
comp2 f g x y == f (g x y)
Evaluation of (.) (.) (.)
-- comp
(.) = \f -> \g -> x -> f (g x)
-- evaluate
(.) (.) (.)
(\f -> \g -> \x -> f (g x)) (.) (.)
(\g -> \x -> (.) (g x)) (.)
\x -> (.) ((.) x)
\x -> (\f -> \g -> \x' -> f (g x')) ((.) x)
\x -> \g -> \x' -> ((.) x) (g x')
\x -> \g -> \x' -> ((\f -> \g' x'' -> f (g' x'')) x) (g x')
\x -> \g -> \x' -> (\g' -> \x'' -> x (g' x'')) (g x')
\x -> \g -> \x' -> \x'' -> x ((g x') x'')
\x -> \g -> \x' -> \x'' -> x (g x' x'')
-- alpha rename
\f -> \g -> \x -> \y -> f (g x y)
-- collapse lambdas
\f g x y -> f (g x y)
Understanding a pattern of (.) compositions
-- comp2
comp2 = (.) (.) (.)
comp2 f g x y == f (g x y)
-- comp3
comp3 = (.) (.) comp2
comp3 f g x y z == f (g x y z)
-- comp4
comp4 = (.) (.) comp3
comp4 f g w x y z == f (g w x y z)

how to prove the associative law of the compositon operation (.) in Haskell

say ,
f :: a -> b
g :: b -> c
h :: c -> d
why the equation
h.(g.f) = (h.g).f
is right?
how to prove it?
and the composition operation is just a basic operation in Haskell,
or we can get one by ourselves? if so how to achieve it?
You can define the composition operator yourself as follows:
(.) :: (b -> c) -> (a -> b) -> a -> c
g . f = \x -> g (f x)
Now, to prove associativity:
lhs = h . (g . f)
= \x -> h ((g . f) x) -- substitution
= \x -> h ((\y -> g (f y)) x) -- substitution
= \x -> h (g (f x)) -- beta reduction
rhs = (h . g) . f
= \x -> (h . g) (f x) -- substitution
= \x -> (\y -> h (g y)) (f x) -- substitution
= \x -> h (g (f x)) -- beta reduction
Now, we have lhs = rhs. QED.

Applicative functors: why can fmap take a function with more than one argument?

I am getting into Haskell and found the book "learn you a Haskell" most helpful. I am up to the section on applicative functors.
I am puzzled by the following as it appears in the book:
(\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
which yields the output:
[8.0,10.0,2.5]
First of all, I have confirmed my suspicion in ghci in regards to precedence of the operators, so that the above equals the following ugly statement:
(((\x y z -> [x,y,z]) <$> (+3)) <*> (*2) <*> (/2)) $ 5
So from that it becomes clear that the first thing that happens is the fmap call via the (<$>) infix operator.
And this is the core of what boggles my mind currently.
The definition of fmap (here shown as infix (<$>)) is:
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
But in the equation I am struggling with, (\x y z -> [x, y, z]) takes three arguments, not just one. So how could the first argument of type (a -> b) be satisfied?
I think it might have to do with partial application / currying but I cannot figure it out. I would greatly appreciate an explanation. Hope I have formulated the question well enough.
Simple answer: there are no functions with multiple arguments in Haskell!
There are two candidates for what you might call "dyadic function": a function that takes a (single!) tuple, and – by far prevalent in Haskell – curried functions. Those take just one argument, but the result is a function again.
So, to figure out what e.g. fmap (+) does, let's write
type IntF = Int -> Int
-- (+) :: Int -> IntF
-- fmap :: ( a -> b ) -> f a -> f b
-- e.g.:: (Int->IntF) -> f Int->f IntF
Test it yourself in GHCi:
Prelude> type IntF = Int -> Int
Prelude> let (#) = (+) :: Int -> IntF
Prelude> :t fmap (#)
fmap (#) :: Functor f => f Int -> f IntF
Consider a function of type
f :: a -> b -> c -> d
where d is any other type. Due to currying, this can be thought of as a function with the following type
f :: a -> (b -> c -> d)
i.e. a function that takes an a and returns function of type b -> c -> d. If you apply fmap, you have
-- the type of fmap, which is also :: (a -> r) -> (f a -> f r)
fmap :: Functor f => (a -> r) -> f a -> f r
-- the type of f
f :: a -> (b -> c -> d)
-- so, setting r = b -> c -> d
fmap f :: f a -> f (b -> c -> d)
Which is now of the right type to be used as the left-hand argument to (<*>).
Because you can take a 3-argument function, feed it just one argument, and this results in a 2-argument function. So you're going to end up with a list of 2-argument functions. You can then apply one more argument, ending up with a list of 1-argument functions, and finally apply the last argument, whereupon you end up with a list of ordinary numbers.
Incidentally, this is why Haskell has curried functions. It makes it easy to write constructs like this one which work for any number of function arguments. :-)
I personally find the applicative functor instance for functions a bit strange. I'll walk you through this example to try to understand intuitively what's going on:
>>> :t (\x y z -> [x, y, z]) <$> (+3)
... :: Num a => a -> a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3)) 1 2 3
[4,2,3]
This applies (+3) to the first parameter of the inner function. The other 2 outer parameters are passed to the inner function unmodified.
Let's add an applicative:
>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2)
... :: Num a => a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3) <*> (*2)) 1 2
[4,2,2]
This applies (+3) to the first argument as before. With the applicative, the first outer parameter (1) is applied (*2) and passed as the second parameter of the inner function. The second outer parameter is passed unmodified to the inner function as its third parameter.
Guess what happens when we use another applicative:
>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2)
... :: Fractional a => a -> [a]
>>> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 1
[4.0,2.0,0.5]
3 applications to the same parameter passed as 3 arguments to the inner function.
It's not theoretically solid explanation, but it can give an intuition about how the applicative instance of functions works.
Background
Let's start with the definition of the <*> and pure for functions as an instance of Applicative. For pure, it will take any garbage value, and return x. For <*>, you can think of it as applying x to f, getting a new function out of it, then applying it to the output of g x.
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
Now, let's look at the definition of <$>. It is just an infix version of fmap.
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
f <$> x = fmap f x
Recall that fmap has the following implementation:
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
Proving that f <$> x is just pure f <*> x
Let's start with pure f <*> x. Replace pure f with (\_ -> f).
pure f <*> x
= (\_ -> f) <*> x
Now, let's apply the definition of <*>, which is f <*> g = \q -> f q (g q).
(\_ -> f) <*> x
= \q -> (\_ -> f) q (x q)
Notice we can simplify (\_ -> f) q as just f. The function takes in whatever value we give it, and returns f.
\q -> (\_ -> f) q (x q)
= \q -> f (x q)
That looks just like our definition of fmap! And the <$> operator is just infix fmap.
\q -> f (x q)
= fmap f x
= f <$> x
Let's keep this in mind: f <$> g is just pure f <*> g.
Understanding (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
First step is to rewrite the left side of expression to use <*> instead of <$>. Using what we just proved in in the previous section:
(\x y z -> [x, y, z]) <$> (+3)
= pure (\x y z -> [x, y, z]) <*> (+3)
So the full expression becomes
pure (\x y z -> [x, y, z]) <*> (+3) <*> (*2) <*> (/2) $ 5
Let's simplify the first operator using the definition of <*>
pure (\x y z -> [x, y, z]) <*> (+3)
= \a -> f a (g a) --substitute f and g
= \a -> pure (\x y z -> [x, y, z]) a ((+3) a)
Now let's substitute pure x with (\_ -> x). Observe that a becomes the garbage value that's used as _, and is consumed to return the function (\x y z -> [x, y, z]).
\a -> (\_-> (\x y z -> [x, y, z])) a ((+3) a)
= \a -> (\x y z -> [x, y, z]) ((+3) a)
Now let's look back at the full expression, and tackle the next <*>. Again, let's apply the definition of <*>.
(\a -> (\x y z -> [x, y, z]) ((+3) a)) <*> (*2)
= \b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)
Finally, let's repeat this one last time for the final <*>.
(\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) <*> (/2)
= \c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)
Notice that it's a function that takes a single value. We'll feed it 5.
(\c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)) 5
(\5 -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 ((/2) 5))
(\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 (2.5 )
(\5 -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 ((*2) 5)) (2.5 )
(\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 (10 ) (2.5 )
(\5 -> (\x y z -> [x, y, z]) ((+3) 5)) (10 ) (2.5 )
(\x y z -> [x, y, z]) (8 ) (10 ) (2.5 )
(\x y z -> [x, y, z]) (8) (10) (2.5)
= [8, 10, 2.5]
And that's how we get the final answer.

How can I understand "(.) . (.)"?

I believe I understand fmap . fmap for Functors, but on functions it's hurting my head for months now.
I've seen that you can just apply the definition of (.) to (.) . (.), but I've forgot how to do that.
When I try it myself it always turns out wrong:
(.) f g = \x -> f (g x)
(.) (.) (.) = \x -> (.) ((.) x)
\x f -> (.) ((.) x) f
\x f y -> (((.)(f y)) x)
\x f y g-> (((.)(f y) g) x)
\x f y g-> ((f (g y)) x)
\x f y g-> ((f (g y)) x):: t2 -> (t1 -> t2 -> t) -> t3 -> (t3 -> t1) -> t
If "just applying the definition" is the only way of doing it, how did anybody come up with (.) . (.)?
There must be some deeper understanding or intuition I'm missing.
Coming up with (.) . (.) is actually pretty straightforward, it's the intuition behind what it does that is quite tricky to understand.
(.) gets you very far when rewriting expression into the "pipe" style computations (think of | in shell). However, it becomes awkward to use once you try to compose a function that takes multiple arguments with a function that only takes one. As an example, let's have a definition of concatMap:
concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)
Getting rid of xs is just a standard operation:
concatMap f = concat . map f
However, there's no "nice" way of getting rid of f. This is caused by the fact, that map takes two arguments and we'd like to apply concat on its final result.
You can of course apply some pointfree tricks and get away with just (.):
concatMap f = (.) concat (map f)
concatMap f = (.) concat . map $ f
concatMap = (.) concat . map
concatMap = (concat .) . map
But alas, readability of this code is mostly gone. Instead, we introduce a new combinator, that does exactly what we need: apply the second function to the final result of first one.
-- .: is fairly standard name for this combinator
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) x y = f (g x y)
concatMap = concat .: map
Fine, that's it for motivation. Let's get to the pointfree business.
(.:) = \f g x y -> f (g x y)
= \f g x y -> f ((g x) y)
= \f g x y -> f . g x $ y
= \f g x -> f . g x
Now, here comes the interesting part. This is yet another of the pointfree tricks that usually helps when you get stuck: we rewrite . into its prefix form and try to continue from there.
= \f g x -> (.) f (g x)
= \f g x -> (.) f . g $ x
= \f g -> (.) f . g
= \f g -> (.) ((.) f) g
= \f -> (.) ((.) f)
= \f -> (.) . (.) $ f
= (.) . (.)
As for intuition, there's this very nice article that you should read. I'll paraphrase the part about (.):
Let's think again about what our combinator should do: it should apply f to the result of result of g (I've been using final result in the part before on purpose, it's really what you get when you fully apply - modulo unifying type variables with another function type - the g function, result here is just application g x for some x).
What it means for us to apply f to the result of g? Well, once we apply g to some value, we'll take the result and apply f to it. Sounds familiar: that's what (.) does.
result :: (b -> c) -> ((a -> b) -> (a -> c))
result = (.)
Now, it turns out that composition (our of word) of those combinators is just a function composition, that is:
(.:) = result . result -- the result of result
You can also use your understanding of fmap . fmap.
If you have two Functors foo and bar, then
fmap . fmap :: (a -> b) -> foo (bar a) -> foo (bar b)
fmap . fmap takes a function and produces an induced function for the composition of the two Functors.
Now, for any type t, (->) t is a Functor, and the fmap for that Functor is (.).
So (.) . (.) is fmap . fmap for the case where the two Functors are (->) s and (->) t, and thus
(.) . (.) :: (a -> b) -> ((->) s) ((->) t a) -> ((->) s) ((->) t b)
= (a -> b) -> (s -> (t -> a)) -> (s -> (t -> b))
= (a -> b) -> (s -> t -> a ) -> (s -> t -> b )
it "composes" a function f :: a -> b with a function of two arguments, g :: s -> t -> a,
((.) . (.)) f g = \x y -> f (g x y)
That view also makes it clear that, and how, the pattern extends to functions taking more arguments,
(.) :: (a -> b) -> (s -> a) -> (s -> b)
(.) . (.) :: (a -> b) -> (s -> t -> a) -> (s -> t -> b)
(.) . (.) . (.) :: (a -> b) -> (s -> t -> u -> a) -> (s -> t -> u -> b)
etc.
Your solution diverges when you introduce y. It should be
\x f y -> ((.) ((.) x) f) y :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) ((.) x) f) y z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) x (f y)) z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
-- Or alternately:
\x f y z -> (x . f y) z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> (x (f y z)) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
Which matches the original type signature: (.) . (.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(It's easiest to do the expansion in ghci, where you can check each step with :t expression)
Edit:
The deeper intution is this:
(.) is simply defined as
\f g -> \x -> f (g x)
Which we can simplify to
\f g x -> f (g x)
So when you supply it two arguments, it's curried and still needs another argument to resolve.
Each time you use (.) with 2 arguments, you create a "need" for one more argument.
(.) . (.) is of course just (.) (.) (.), so let's expand it:
(\f0 g0 x0 -> f0 (g0 x0)) (\f1 g1 x1 -> f1 (g1 x1)) (\f2 g2 x2 -> f2 (g2 x2))
We can beta-reduce on f0 and g0 (but we don't have an x0!):
\x0 -> (\f1 g1 x1 -> f1 (g1 x1)) ((\f2 g2 x2 -> f2 (g2 x2)) x0)
Substitute the 2nd expression for f1...
\x0 -> \g1 x1 -> ((\f2 g2 x2 -> f2 (g2 x2)) x0) (g1 x1)
Now it "flips back"! (beta-reduction on f2):
This is the interesting step - x0 is substituted for f2 -- This means that x, which could have been data, is instead a function.
This is what (.) . (.) provides -- the "need" for the extra argument.
\x0 -> \g1 x1 -> (\g2 x2 -> x0 (g2 x2)) (g1 x1)
This is starting to look normal...
Let's beta-reduce a last time (on g2):
\x0 -> \g1 x1 -> (\x2 -> x0 ((g1 x1) x2))
So we're left with simply
\x0 g1 x1 x2 -> x0 ((g1 x1) x2)
, where the arguments are nicely still in order.
So, this is what I get when I do a slightly more incremental expansion
(.) f g = \x -> f (g x)
(.) . g = \x -> (.) (g x)
= \x -> \y -> (.) (g x) y
= \x -> \y -> \z -> (g x) (y z)
= \x y z -> (g x) (y z)
(.) . (.) = \x y z -> ((.) x) (y z)
= \x y z -> \k -> x (y z k)
= \x y z k -> x (y z k)
Which, according to ghci has the correct type
Prelude> :t (.) . (.)
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
Prelude> :t \x y z k -> x (y z k)
\x y z k -> x (y z k)
:: (t1 -> t) -> (t2 -> t3 -> t1) -> t2 -> t3 -> t
Prelude>
While I don't know the origins of this combinator, it is likely that it was
developed for use in combinatory logic, where you work strictly with combinators,
so you can't define things using more convenient lambda expressions. There may be
some intuition that goes with figuring these things out, but I haven't found it.
Most likely, you would develop some level of intuition if you had to do it enough.
It's easiest to write equations, combinators-style, instead of lambda-expressions: a b c = (\x -> ... body ...) is equivalent to a b c x = ... body ..., and vice versa, provided that x does not appear among {a,b,c}. So,
-- _B = (.)
_B f g x = f (g x)
_B _B _B f g x y = _B (_B f) g x y
= (_B f) (g x) y
= _B f (g x) y
= f ((g x) y)
= f (g x y)
You discover this if, given f (g x y), you want to convert it into a combinatory form (get rid of all the parentheses and variable repetitions). Then you apply patterns corresponding to the combinators' definitions, hopefully tracing this derivation backwards. This is much less mechanical/automatic though.

Resources