I'm learning about Haskell and came across this concise, but weird, definition for a function that multiplies three numbers:
volume :: Float -> Float -> Float -> Float
volume = ((*) .) . (*)
I can see from the type signature and by testing it in GHCi that it correctly calculates the product of three numbers, essentially \l w h -> l * w * h.
I know that . is the function composition operator, where (f . g) x is equivalent to f (g x).
I understand that partial application works like (*) 2, which returns a new function that multiplies its argument by 2.
My confusion comes from the ((*) .) part of the expression. It looks like the composition operator . is being partially applied, which is bending my brain a little.
I tried to break it down by applying arguments one by one:
volume l w h = ((*) .) . (*) l w h
This becomes ((*) .) ((* l)) w h because of how . works.
Let multByL = (* l). The expression is now ((*) .) multByL w h.
This is where I get lost. I'm not sure how to evaluate ((*) .) multByL. How does a partially applied composition operator work here? What does it return and how does it then interact with the remaining arguments w and h? I'm not very familiar with the operation order. Maybe area = multByL w will be calculated first? But even in this way, I still get confused while encountering ((*) .) area h. There is only one function, shouldn't composition operator work with two functions?
What exactly is the function ((*) .)? What is its type, and what does it do?
Is there a more intuitive way to "read" this expression from left to right to understand its meaning?
Any help in demystifying this clever piece of code would be greatly appreciated!
Thank you!!!
A quick comment: (*) l becomes (l*), not (*l). For most number types, this distinction doesn't matter.
Generally, an op section has the following semantics:
(op) means \x y -> x op y(e op) means \x -> e op x(op e) means \x -> x op eSo let's start from your expression:
((*) .) (l*) w h
= { expand the `(e .)` section with `e = (*)` }
(\x -> (*) . x) (l*) w h
= { beta reduce }
((*) . (l*)) w h
= { definition of `.` }
(*) ((l*) w) h
= { expand the `(e *)` section with `e = l` }
(*) ((\x -> l*x) w) h
= { beta reduce }
(*) (l*w) h
= { expand the `(*)` section }
(\x y -> x*y) (l*w) h
= { beta reduce }
(l*w)*h
Actually, to be fully precise, the semantics of the two sections (op) and (e op) should not be the lambda expressions I showed, but their eta reduction. But it's pretty rare for that difference to matter, so if you didn't understand that, feel free to ignore it -- by the time it's important, you'll likely know it already.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With