I know that the dot (.) operator is defined to take three arguments(two functions and a value of type "a"), and works by the application of applying the argument to the function on the right, and the function on the left is applied to the output of the application of the left side, like follows:
-- Associativity: infixr 9
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f(g x)
-- Or in another format
f . g = \x -> f(g x)
Now, that's what's confusing with more than one application with the dot operator in a function composition, consider this:
With an infix operator like (+), it takes two arguments of types within the "Num" class.
(+) :: Num a => a -> a -> a
-- An application would look like this:
> 4 + 3
7
-- With more applications composed with (+)
> 4 + 3 + 2
9
-- Which is fine, since the last application
-- still takes two arguments of the legitimate types,
-- which is a value of type within "Num" class, as follows
> (4 + 3) + 2
-- 7 + 2
9
But with the dot operator, as follows. The last function composition with the dot operator should instead take the output of the application "reverse . take 3 $ map (*2) [1..]", which is "[6,4,2]" of the previous function composition.
If that's what I said, the dot operator takes a value instead of a function and a value on the right side, how is it supposed to work?
> replicate 4 . reverse . take 3 $ map (*2) [1..]
[[6,4,2],[6,4,2],[6,4,2],[6,4,2]]
Isn't it supposed to be like follows for its middle step?
-- And it's not going to work, since the dot operator
-- is defined to take three arguments of two functions
-- "(b -> c)" and "(a -> b)"
-- and the value of type "a"
replicate 4 . [6,4,2]
Since an application of dot operator takes three arguments and then returns a result of a value of type "a".
So the part "reverse . take 3 $ map (*2) [1..]" on the left side should be evaluated to "[6,4,2]"
Why is it not?
You are getting confused by currification (more on this later). You can think of
(.)as taking two arguments (as(+)does).Now, because
(.)it is right assoc the two lines of code below are equivalentin order to make this more explicit let me rewrite the code above
Now, the technique we are applying here is called currying. Which can be summarize as "a function which takes two arguments can be re-written as a function taking one argument and returning another function taking one argument."
Some python code for the sake of understanding
Now, all haskell functions are curried, this actually make a lot of sense on a functional language because if simplifies a lot composition and makes the language more ergonomic in most situations (altough, this is an opinion and someothers may ague differently)
Currying can be a headache in the beginning but eventually It fells pretty natural. The best way to overcome it is by doing a lot of exercises and trying to use a lot of partial application