The expression (f . g) x
means f (g x)
.
Coherently, (f . g) x y
means f (g x) y
.
Note how y
is passed as a second parameter to f
, not to g
. The result is not f (g x y)
.
In your case, (sort . (++)) x y
would mean sort ((++) x) y
, which would call sort
with first argument (++) x
(the function which prepends the list x
to its list argument), and with second argument y
. Alas, this is ill-typed since sort
only takes one argument.
Consequently, this is also invalid
appendAndSort x y = (sort . (++)) x y
hence so is this
appendAndSort = sort . (++)
By contrast, ((f .) . g) x y
does work as expected. Let's compute:
((f .) . g) x y
= -- same reasoning as above, y is passed to (f.), not g
(f .) (g x) y
= -- application associates on the left
((f .) (g x)) y
= -- definition of `(f.)`
(f . (g x)) y
= -- definition of .
f ((g x) y)
= -- application associates on the left
f (g x y)
So this really makes y
to be passed to g
(and not f
).
In my opinion the "idiom" (f .) . g
isn't worth using. The pointful \x y -> f (g x y)
is much simpler to read, and not terribly longer.
If you really want, you can define a custom composition operator to handle the two-argument case.
(.:) f g = \x y -> f (g x y)
Then, you can write
appendAndSort = sort .: (++)
*binary* functionahem a *doubly-nested* functor. thus you need to use the [*doubly nested* `fmap`](https://stackoverflow.com/a/34701189/849891) i.e. `appendAndSort = fmap (fmap sort) (++)` which happens to be the same as `(.) ((.) sort) (++)` or with [operator section syntax](https://stackoverflow.com/search?q=user%3A849891+%22laws+of+operator+sections%22) `(.) (sort .) (++) = ((sort .) . (++))` (outer parens optional, in the last one). – Will Ness Feb 08 '22 at 17:46