I'd like to understand how simple arithmetic expressions are compiled into GHC 9.0.1 Core
For this purpose, I've isolated the same declaration,
f = \x y -> sqrt x + y
and compiled it at two different types:
f :: Double -> Double -> Double
f :: Floating a => a -> a -> a
resulting in these very different beasts :
\ (x [Dmd=<S,1*U(U)>] :: Double) (y [Dmd=<S,1*U(U)>] :: Double) ->
case x of { D# x ->
case y of { D# y -> D# (+## (sqrtDouble# x) y) }
}
in the monomorphic case and
\ (@a)
($dFloating_a22t [Dmd=<S(S(S(C(C(S))LLLLLL)LLL)LLLLLLLLLLLLLLLLLLLLLL),U(1*U(1*U(1*C1(C1(U)),A,A,A,A,A,A),A,A,A),A,A,A,1*C1(U),A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A)>]
:: Floating a)
(eta_B0 :: a)
(eta_B1 :: a) ->
+ @a
($p1Fractional @a ($p1Floating @a $dFloating_a22t))
(sqrt @a $dFloating_a22t eta_B0)
eta_B1
in the polymorphic one.
Besides the demand/strictness annotations (the Dmd=... stuff), which deserve a separate question, I'm curious to understand the different shape of the two resulting expressions. In the monomorphic case, we do a nested case match, but in the other one we only see the operators annotated with type variables (@a) and constraints ($p1Fractional etc.)
I guess I should add that the above output is obtained via a custom plugin pass that runs at the very end of the preexisting CoreToDo list, so after desugaring etc. (that's about all I know about this subject btw)
Is there a GHC stage in which these two compiled versions are structurally identical?