[GHC] #12791: Superclass methods could be more aggressively specialised.
GHC
ghc-devs at haskell.org
Thu Nov 17 03:15:02 UTC 2016
#12791: Superclass methods could be more aggressively specialised.
-------------------------------------+-------------------------------------
Reporter: mpickering | Owner: danharaj
Type: bug | Status: new
Priority: normal | Milestone:
Component: Compiler | Version: 8.0.1
Resolution: | Keywords:
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: | Blocking:
Related Tickets: | Differential Rev(s): Phab:D2714
Wiki Page: |
-------------------------------------+-------------------------------------
Comment (by danharaj):
Hi,
The motivation of this patch is that the compiler can produce more
efficient code if the constraint solver used top-level instance
declarations to solve constraints that are currently solved givens and
their superclasses. In particular, as it currently stands, the compiler
imposes a performance penalty on the common use-case where superclasses
are bundled together for user convenience. The performance penalty applies
to constraint synonyms as well. This example illustrates the issue:
{{{#!hs
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts,
FunctionalDependencies #-}
module A where
import Data.Monoid
class (Monoid w, Monad m) => MonadWriter w m | m -> w where
tell :: w -> m ()
f :: MonadWriter Any m => [Any] -> m ()
f xs = tell (mconcat xs)
}}}
This produces the following core on GHC 8.0.2 by running `ghc A.hs -ddump-
simpl -dsuppress-idinfo`:
{{{
f :: forall (m_aJV :: * -> *).
MonadWriter Any m_aJV =>
[Any] -> m_aJV ()
f =
\ (@ (m_aZM :: * -> *))
($dMonadWriter_aZN :: MonadWriter Any m_aZM)
(eta_B1 :: [Any]) ->
tell
@ Any
@ m_aZM
$dMonadWriter_aZN
(mconcat
@ Any (A.$p1MonadWriter @ Any @ m_aZM $dMonadWriter_aZN) eta_B1)
}}}
With the patch, the code produced:
{{{
f :: forall (m :: * -> *). MonadWriter Any m => [Any] -> m ()
f =
\ (@ (m :: * -> *))
($dMonadWriter_a12F :: MonadWriter Any m)
(xs_aLg :: [Any]) ->
tell
@ Any
@ m
$dMonadWriter_a12F
(mconcat @ Any Data.Monoid.$fMonoidAny xs_aLg)
}}}
The performance gains possible are perhaps more starkly present in the
following example from the comment thread, which here I have compiled also
with `-O2`:
{{{#!hs
{-# LANGUAGE ConstraintKinds, MultiParamTypeClasses, FlexibleContexts #-}
module B where
class M a b where m :: a -> b
type C a b = (Num a, M a b)
f :: C Int b => b -> Int -> Int
f _ x = x + 1
}}}
Output without the patch:
{{{
f :: forall b_arz. C Int b_arz => b_arz -> Int -> Int
f =
\ (@ b_a1EB) ($d(%,%)_a1EC :: C Int b_a1EB) _ (eta1_B1 :: Int) ->
+ @ Int
(GHC.Classes.$p1(%,%) @ (Num Int) @ (M Int b_a1EB) $d(%,%)_a1EC)
eta1_B1
B.f1
}}}
Output with the patch:
{{{
f :: forall b. C Int b => b -> Int -> Int
f =
\ (@ b) _ _ (x_azg :: Int) ->
case x_azg of { GHC.Types.I# x1_a1DP ->
GHC.Types.I# (GHC.Prim.+# x1_a1DP 1#)
}
}}}
However, there is a reason why the solver does not simply try to solve
such constraints with top-level instances. If the solver finds a relevant
instance declaration in scope, that instance may require a context that
can't be solved for. As pointed out in the thread, a good example of this
is:
{{{
f :: Ord [a] => ...
f x = ..Need Eq [a]...
}}}
If we have `instance Eq a => Eq [a]` in scope and we tried to use it, we
would be left with the obligation to solve the constraint `Eq a`, which we
cannot. So the patch must be conservative in its attempt to use an
instance declaration to solve the constraint we're interested in. The rule
I have applied is as was previously mentioned in the comment thread: The
solver gives up on using an instance declaration to solve a given
constraint if doing so would produce more work to be done; we make no
attempt even if the new constraints are also solvable with instances.
Precisely: The intent of the patch is to only attempt to solve constraints
of the form `C t1 ... tn` and only with instances with no context.
This example illustrates the conservative behavior:
{{{#!hs
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts,
FunctionalDependencies #-}
module C where
class Eq a => C a b | b -> a where
m :: b -> a
f :: C [Int] b => b -> Bool
f x = m x == []
}}}
The core output without the patch:
{{{
f :: forall b_a1ka. C [Int] b_a1ka => b_a1ka -> Bool
f =
\ (@ b_a1rX) ($dC_a1rY :: C [Int] b_a1rX) (eta_B1 :: b_a1rX) ->
==
@ [Int]
(C.$p1C @ [Int] @ b_a1rX $dC_a1rY)
(m @ [Int] @ b_a1rX $dC_a1rY eta_B1)
(GHC.Types.[] @ Int)
}}}
The core output with the patch:
{{{
f :: forall b. C [Int] b => b -> Bool
f =
\ (@ b) ($dC_a1sq :: C [Int] b) (eta_B1 :: b) ->
==
@ [Int]
(C.$p1C @ [Int] @ b $dC_a1sq)
(m @ [Int] @ b $dC_a1sq eta_B1)
(GHC.Types.[] @ Int)
}}}
Even though we also have an instance for `Eq Int` in scope, the solver
does not even try.
The impact of this patch on typeability is simple: there should be no
change whatsoever. Every program considered well-typed without the patch
should remain well-typed with it, and every program considered ill-typed
without the patch should remain ill-typed.
There is a possible change in semantics with this patch because the solver
is now choosing different solutions for certain constraints, however there
should be no difference in behavior up to confluence of proofs. There is a
possible interaction with overlapping instances and my intent was to
preserve the current behavior of overlapping instances exactly. I expect
that code that uses Incoherent Instances could see a change in behavior
with this patch.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/12791#comment:19>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list