#14955: Musings on manual type class desugaring -------------------------------------+------------------------------------- Reporter: mpickering | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 8.2.2 Resolution: | Keywords: SpecConstr Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj): (2) is slow becuase we don't (yet) do cross-module !SpecConstr. If `dors` was `INLINEABLE` then in principle !SpecConstr could specialise it in `Main`, in a similar way to `Specialise`. But that is a whole new thing. (A very good thing, incidentally: that is Trac #10346. (3) jolly well ought to be fast. That's a bug. Here is what is happening. The `INLINABLE` pragma on `pors` (for (3)) or `porsProxy` (for (4)) should arrange that these functions can be speicalised in a separate module. But in fact `pors` is worker/wrappered by the demand analyser! We get this in the .hi file: {{{ pors :: PropProxy r => [r] -> r {- Arity: 2, HasNoCafRefs, Strictness: <S,1*U(C(C1(U)),A,1*U,A)><S,1*U>, Inline: [0], Unfolding: InlineRule (2, True, False) (\ @ r (w :: PropProxy r) (w1 :: [r]) -> case w `cast` (N:PropProxy[0] <r>_N) of ww { PropDict ww1 ww2 ww3 ww4 -> $wpors @ r ww1 ww3 w1 }) -} porsProxy :: PropProxy2 r => [r] -> r {- Arity: 2, HasNoCafRefs, Strictness: <S(SL),U(U(C(C1(U)),A,U,A),A)><S,1*U>, Inline:, Unfolding(loop-breaker): <stable> (\ @ r ($dPropProxy2 :: PropProxy2 r) (ds :: [r]) -> case ds of wild { [] -> case propDict2 @ r $dPropProxy2 of wild1 { PropDict ds1 ds2 ds3 ds4 -> ds3 } : o os -> case propDict2 @ r $dPropProxy2 of wild1 { PropDict ds1 ds2 ds3 ds4 -> ds1 o (porsProxy @ r $dPropProxy2 os) } }) -} }}} So `porsProxy` can be specialised in the calling file -- good. And so can `pors` -- but the specialised version of its code is silly, just a call to the un-specialised `$wpors`; still a higher order function, which is bad bad bad. But I fixed this more 6 years ago. See this Note in `WwLib`: {{{ Note [Do not unpack class dictionaries] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If we have f :: Ord a => [a] -> Int -> a {-# INLINABLE f #-} and we worker/wrapper f, we'll get a worker with an INLINABLE pragma (see Note [Worker-wrapper for INLINABLE functions] in WorkWrap), which can still be specialised by the type-class specialiser, something like fw :: Ord a => [a] -> Int# -> a BUT if f is strict in the Ord dictionary, we might unpack it, to get fw :: (a->a->Bool) -> [a] -> Int# -> a and the type-class specialiser can't specialise that. An example is Trac #6056. Moreover, dictionaries can have a lot of fields, so unpacking them can increase closure sizes. Conclusion: don't unpack dictionaries. }}} So why isn't this preventing the w/w for `pors`? Because the bit that prevents the unpacking is here: {{{ deepSplitProductType_maybe fam_envs ty | let (co, ty1) = topNormaliseType_maybe fam_envs ty `orElse` (mkRepReflCo ty, ty) , Just (tc, tc_args) <- splitTyConApp_maybe ty1 , Just con <- isDataProductTyCon_maybe tc , not (isClassTyCon tc) -- See Note [Do not unpack class dictionaries] , let arg_tys = dataConInstArgTys con tc_args strict_marks = dataConRepStrictness con = Just (con, tc_args, zipEqual "dspt" arg_tys strict_marks, co) }}} Ha! We do `topNormaliseType_maybe` before checking `isClassTyCon`. And when the class is a newtype, that `topNormaliseType_maybe` unwraps it. It's easy to fix: just move the test earlier. Patch coming -- but maybe after next week when I'm on holiday. -- Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/14955#comment:2> GHC <http://www.haskell.org/ghc/> The Glasgow Haskell Compiler